diff --git a/action/ajax.php b/action/ajax.php new file mode 100644 --- /dev/null +++ b/action/ajax.php @@ -0,0 +1,103 @@ +hlp =& plugin_load('helper','davcard'); + } + + function register(Doku_Event_Handler $controller) { + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); + } + + function handle_ajax_call_unknown(Doku_Event $event, $param) { + if($event->data != 'plugin_davcard') return; + + $event->preventDefault(); + $event->stopPropagation(); + global $INPUT; + + $action = trim($INPUT->post->str('action')); + $id = trim($INPUT->post->str('id')); + $name = trim($INPUT->post->str('name')); + + if(!checkSecurityToken()) + { + echo "CSRF Attack."; + return; + } + + $data = array(); + + $data['result'] = false; + $data['html'] = $this->getLang('unknown_error'); + + // Parse the requested action + switch($action) + { + case 'getContact': + if($name !== '') + { + $info = $this->hlp->getContactByName($id, $name); + if($info === false) + { + $data['html'] = $this->getLang('contact_not_found'); + break; + } + $data['result'] = true; + if($info['formattedname'] !== '') + $data['html'] = 'Name: '.$info['formattedname'].'
'; + if(count($info['tel']) > 0) + { + foreach($info['tel'] as $type => $nr) + { + $data['html'] .= $type.': '.$nr.'
'; + } + } + if(count($info['addr']) > 0) + { + foreach($info['addr'] as $type => $addr) + { + $data['html'] .= $type.': '.join('
', $addr); + $data['html'] .= '
'; + } + } + if(count($info['mail']) > 0) + { + foreach($info['mail'] as $type => $mail) + { + $data['html'] .= $type.': '.$mail.'
'; + } + } + + } + else + { + $data['html'] = $this->hlp-->getContactByUri($id); + } + break; + } + + // If we are still here, JSON output is requested + + //json library of DokuWiki + require_once DOKU_INC . 'inc/JSON.php'; + $json = new JSON(); + + //set content type + header('Content-Type: application/json'); + echo $json->encode($data); + } + +} diff --git a/action/jsinfo.php b/action/jsinfo.php new file mode 100644 --- /dev/null +++ b/action/jsinfo.php @@ -0,0 +1,23 @@ +register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'add_jsinfo_information'); + } + + /** + * Add the language variable to the JSINFO variable + */ + function add_jsinfo_information(Doku_Event $event, $param) { + global $JSINFO; + + $JSINFO['plugin']['davcard']['sectok'] = getSecurityToken(); + } +} diff --git a/conf/default.php b/conf/default.php new file mode 100644 --- /dev/null +++ b/conf/default.php @@ -0,0 +1,8 @@ + + */ + + diff --git a/conf/metadata.php b/conf/metadata.php new file mode 100644 --- /dev/null +++ b/conf/metadata.php @@ -0,0 +1,7 @@ + + */ + diff --git a/helper.php b/helper.php new file mode 100644 --- /dev/null +++ b/helper.php @@ -0,0 +1,114 @@ +getLang('no_wdc'); + $connectionId = str_replace('webdav://', '', $id); + $settings = $wdc->getConnection($connectionId); + + if($settings === false) + return $this->getLang('settings_not_found'); + if($settings['type'] !== 'contacts') + return $this->getLang('wrong_type'); + + $entries = $wdc->getAddressbookEntries($connectionId); + foreach($entries as $entry) + { + if(trim($entry['formattedname']) == $name) + { + $info = $this->parseVcard($entry['contactdata']); + return $info; + } + } + } + return false; + } + + public function getContactByUri($id) + { + + } + + private function parseVcard($card) + { + require_once(DOKU_PLUGIN.'davcard/vendor/autoload.php'); + + $vObject = \Sabre\VObject\Reader::read($card); + + $formattedname = ''; + $structuredname = ''; + $tel = array(); + $addr = array(); + $mail = array(); + + if(isset($vObject->FN)) + $formattedname = (string)$vObject->FN; + + if(isset($vObject->N)) + $structuredname = join(';', $vObject->N->getParts()); + + if(isset($vObject->TEL)) + { + foreach($vObject->TEL as $number) + { + if(isset($number['TYPE'])) + $tel[(string)$number['TYPE']] = (string)$number; + else + $tel[] = (string)$number; + } + } + + if(isset($vObject->ADR)) + { + foreach($vObject->ADR as $adr) + { + if(isset($adr['TYPE'])) + $addr[(string)$adr['TYPE']] = $adr->getParts(); + else + $addr[] = $adr->getParts(); + } + } + + if(isset($vObject->EMAIL)) + { + foreach($vObject->EMAIL as $email) + { + if(isset($email['TYPE'])) + $mail[(string)$email['TYPE']] = (string)$email; + else + $mail[] = (string)$email; + } + } + + + return array( + 'formattedname' => $formattedname, + 'structuredname' => $structuredname, + 'tel' => $tel, + 'mail' => $mail, + 'addr' => $addr + ); + } + +} diff --git a/lang/en/lang.php b/lang/en/lang.php new file mode 100644 --- /dev/null +++ b/lang/en/lang.php @@ -0,0 +1,13 @@ + + */ +$lang['unknown_error'] = 'Unknown Error'; +$lang['id_name_not_set'] = 'Either ID or Name must be set'; +$lang['loading_via_ajax'] = 'Loadig Contact Data...'; +$lang['wdc'] = 'Loading webdavclient PlugIn failed.'; +$lang['settings_not_found'] = 'The requested WebDAV connection was not found'; +$lang['wrong_type'] = 'The requested WebDAV connection is not of type contact'; +$lang['contact_not_found'] = 'The requested contact was not found'; diff --git a/lang/en/settings.php b/lang/en/settings.php new file mode 100644 --- /dev/null +++ b/lang/en/settings.php @@ -0,0 +1,6 @@ + + */ diff --git a/plugin.info.txt b/plugin.info.txt new file mode 100644 --- /dev/null +++ b/plugin.info.txt @@ -0,0 +1,7 @@ +base davcard +author Andreas Boehler +email dev@aboehler.at +date 2016-05-12 +name Addressbook PlugIn with CardDAV client support +desc Show contact information from a CardDAV address book (needs webdavclient) +url http://www.dokuwiki.org/plugin:davcard diff --git a/script.js b/script.js new file mode 100644 --- /dev/null +++ b/script.js @@ -0,0 +1,28 @@ +/** + * Initialize the DAVCard script, attaching some event handlers and triggering + * the initial load of the fullcalendar JS + */ + +jQuery(function() { + jQuery('div.plugin_davcard').each(function() { + var $div = jQuery(this); + var id = $div.data('cardid'); + var name = $div.data('name'); + if (!id && !name) return; + + jQuery.post( + DOKU_BASE + 'lib/exe/ajax.php', + { + call: 'plugin_davcard', + id: id, + action: 'getContact', + name: name, + sectok: JSINFO.plugin.davcard['sectok'] + }, + function(data) + { + $div.html(data['html']); + }); + + }); +}); diff --git a/syntax/card.php b/syntax/card.php new file mode 100644 --- /dev/null +++ b/syntax/card.php @@ -0,0 +1,97 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); +require_once(DOKU_PLUGIN.'syntax.php'); + +class syntax_plugin_davcard_card extends DokuWiki_Syntax_Plugin { + + protected $hlp = null; + + // Load the helper plugin + public function syntax_plugin_davcard_card() { + $this->hlp =& plugin_load('helper', 'davcard'); + } + + + /** + * What kind of syntax are we? + */ + function getType(){ + return 'substition'; + } + + /** + * What about paragraphs? + */ + function getPType(){ + return 'normal'; + } + + /** + * Where to sort in? + */ + function getSort(){ + return 165; + } + + /** + * Connect pattern to lexer + */ + function connectTo($mode) { + $this->Lexer->addSpecialPattern('\{\{davcard>[^}]*\}\}',$mode,'plugin_davcard_card'); + } + + /** + * Handle the match + */ + function handle($match, $state, $pos, Doku_Handler $handler){ + global $ID; + $options = trim(substr($match,10,-2)); + $options = explode(',', $options); + $data = array('name' => '', + 'id' => '', + ); + foreach($options as $option) + { + list($key, $val) = explode('=', $option); + $key = strtolower(trim($key)); + $val = trim($val); + switch($key) + { + default: + $data[$key] = $val; + } + } + if($data['id'] === '' && $data['name'] === '') + { + msg($this->getLang('id_name_not_set'), -1); + } + return $data; + } + + /** + * Create output + */ + function render($format, Doku_Renderer $R, $data) { + if($format != 'xhtml') return false; + + $R->doc .= '
'; + $R->doc .= ''; + $R->doc .= $this->getLang('loading_via_ajax').'
'; + + } + + + +} + +// vim:ts=4:sw=4:et:enc=utf-8: diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2016 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/sabre/vobject/lib'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,45 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,70 @@ +[ + { + "name": "sabre/vobject", + "version": "3.5.2", + "version_normalized": "3.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-vobject.git", + "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/b7d6005b9f8e18bfe2b953d9847df0b3e4098441", + "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "squizlabs/php_codesniffer": "*" + }, + "time": "2016-04-24 07:05:24", + "bin": [ + "bin/vobject", + "bin/generate_vcards" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\VObject\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Dominik Tobschall", + "email": "dominik@fruux.com", + "homepage": "http://tobschall.de/", + "role": "Developer" + } + ], + "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "homepage": "http://sabre.io/vobject/", + "keywords": [ + "VObject", + "iCalendar", + "jCal", + "jCard", + "vCard" + ] + } +] diff --git a/vendor/sabre/vobject/.gitignore b/vendor/sabre/vobject/.gitignore new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/.gitignore @@ -0,0 +1,18 @@ +# Composer stuff +vendor/ +composer.lock +tests/cov/ +tests/temp + +#vim +.*.swp + +#binaries +bin/phpunit +bin/phpcs + +# Development stuff +testdata/ + +# OS X +.DS_Store diff --git a/vendor/sabre/vobject/.travis.yml b/vendor/sabre/vobject/.travis.yml new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/.travis.yml @@ -0,0 +1,19 @@ +language: php +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + allow_failures: + - php: hhvm + +script: + - phpunit --configuration tests/phpunit.xml + - ./bin/phpcs -p --standard=tests/phpcs/ruleset.xml lib/ + + +before_script: composer install diff --git a/vendor/sabre/vobject/ChangeLog.md b/vendor/sabre/vobject/ChangeLog.md new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/ChangeLog.md @@ -0,0 +1,617 @@ +ChangeLog +========= + +3.5.2 (2016-04-24) +----------------- + +* #312: Backported a fix related to iTip processing of events with timezones, + without a master event. + + +3.5.1 (2016-04-06) +------------------ + +* #309: When expanding recurring events, the first event should also have a + `RECURRENCE-ID` property. +* #306: iTip REPLYs to the first instance of a recurring event was not handled + correctly. + + +3.5.0 (2016-01-11) +------------------ + +* This release supports PHP 7, contrary to 3.4.x versions. +* BC Break: `Sabre\VObject\Property\Float` has been renamed to + `Sabre\VObject\Property\FloatValue`. +* BC Break: `Sabre\VObject\Property\Integer` has been renamed to + `Sabre\VObject\Property\IntegerValue`. + + +3.4.9 (2016-01-11) +------------------ + +* This package now specifies in composer.json that it does not support PHP 7. + For PHP 7, use version 3.5.x or 4.x. + + +3.4.8 (2016-01-04) +------------------ + +* #284: When generating `CANCEL` iTip messages, we now include `DTEND`. + (@kewisch). + + +3.4.7 (2015-09-05) +------------------ + +* #253: Handle `isInTimeRange` for recurring events that have 0 valid + instances. (@DominikTo, @migrax). + + +3.4.6 (2015-08-06) +------------------ + +* #250: Recurring all-day events are incorrectly included in time range + requests when not using UTC in the time range. (@armin-hackmann) + + +3.4.5 (2015-06-02) +------------------ + +* #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property + would throw an error. + + +3.4.4 (2015-05-27) +------------------ + +* #228: Fixed a 'party crasher' bug in the iTip broker. This would break + scheduling in some cases. + + +3.4.3 (2015-05-19) +------------------ + +* #219: Corrected validation of `EXDATE` properties with more than one value. +* #212: `BYSETPOS` with values below `-1` was broken and could cause infinite + loops. +* #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist) +* #216: `ENCODING` parameter is now validated for all document types. +* #217: Initializing vCard `DATE` objects with a PHP DateTime object will now + work correctly. (@thomascube) + + +3.4.2 (2015-02-25) +------------------ + +* #210: iTip: Replying to an event without a master event was broken. + + +3.4.1 (2015-02-24) +------------------ + +* A minor change to ensure that unittests work correctly in the sabre/dav + test-suite. + + +3.4.0 (2015-02-23) +------------------ + +* #196: Made parsing recurrence rules a lot faster on big calendars. +* Updated windows timezone mappings to latest unicode version. +* #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan) +* #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan) +* #205: Improving handling of multiple `EXDATE` when processing iTip changes. + (@armin-hackmann) +* #187: Fixed validator rules for `LAST-MODIFIED` properties. +* #188: Retain floating times when generating instances using + `Recur\EventIterator`. +* #203: Skip tests for timezones that are not supported on older PHP versions, + instead of a hard fail. +* #204: Dealing a bit better with vCard date-time values that contained + milliseconds. (which is normally invalid). (@armin-hackmann) + + +3.3.5 (2015-01-09) +------------------ + +* #168: Expanding calendars now removes objects with recurrence rules that + don't have a valid recurrence instance. +* #177: SCHEDULE-STATUS should not contain a reason phrase, only a status + code. +* #175: Parser can now read and skip the UTF-8 BOM. +* #179: Added `isFloating` to `DATE-TIME` properties. +* #179: Fixed jCal serialization of floating `DATE-TIME` properties. +* #173: vCard converter failed for `X-ABDATE` properties that had no + `X-ABLABEL`. +* #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules + specific for CalDAV/CardDAV servers. +* #176: A missing `UID` is no longer an error, but a warning for the vCard + validator, unless `PROFILE_CARDDAV` is specified. + + +3.3.4 (2014-11-19) +------------------ + +* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and + vice-versa when converting to/from vCard 4. +* #154: It's now possible to easily select all vCard properties belonging to + a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann) +* #156: Simpler way to check if a string is UTF-8. (@Hywan) +* Unittest improvements. +* #159: The recurrence iterator, freebusy generator and iCalendar DATE and + DATE-TIME properties can now all accept a reference timezone when working + floating times or all-day events. +* #159: Master events will no longer get a `RECURRENCE-ID` when expanding. +* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding. +* #163: Added a `getTimeZone()` method to `VTIMEZONE` components. + + +3.3.3 (2014-10-09) +------------------ + +* #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the + original event. +* #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected. +* #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is + set to support the inbox feature of iOS. +* #147: Bugs related to scheduling all-day events. +* #148: Ignore events that have attendees but no organizer. +* #149: Avoiding logging errors during timezone detection. This is a workaround + for a PHP bug. +* Support for "Line Islands Standard Time" windows timezone. +* #154: Correctly work around vCard parameters that have a value but no name. + + +3.3.2 (2014-09-19) +------------------ + +* Changed: iTip broker now sets RSVP status to false when replies are received. +* #118: iTip Message now has a `getScheduleStatus()` method. +* #119: Support for detecting 'significant changes'. +* #120: Support for `SCHEDULE-FORCE-SEND`. +* #121: iCal demands parameters containing the + sign to be quoted. +* #122: Don't generate REPLY messages for events that have been cancelled. +* #123: Added `SUMMARY` to iTip messages. +* #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`). +* #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`. +* #131: RRULE that doesn't provide a single valid instance now throws an + exception. +* #136: Validator rejects *all* control characters. We were missing a few. +* #133: Splitter objects will throw exceptions when receiving incompatible + objects. +* #127: Attendees who delete recurring event instances events they had already + declined earlier will no longer generate another reply. +* #125: Send CANCEL messages when ORGANIZER property gets deleted. + + +3.3.1 (2014-08-18) +------------------ + +* Changed: It's now possible to pass DateTime objects when using the magic + setters on properties. (`$event->DTSTART = new DateTime('now')`). +* #111: iTip Broker does not process attendee adding events to EXDATE. +* #112: EventIterator now sets TZID on RECURRENCE-ID. +* #113: Timezone support during creation of iTip REPLY messages. +* #114: VTIMEZONE is retained when generating new REQUEST objects. +* #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip + broker. This improves evolution support. +* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into + SCHEDULE-STATUS. + + +3.3.0 (2014-08-07) +------------------ + +* We now use PSR-4 for the directory structure. This means that everything + that was used to be in the `lib/Sabre/VObject` directory is now moved to + `lib/`. If you use composer to load this library, you shouldn't have to do + anything about that though. +* VEVENT now get populated with a DTSTAMP and UID property by default. +* BC Break: Removed the 'includes.php' file. Use composer instead. +* #103: Added support for processing [iTip][iTip] messages. This allows a user + to parse incoming iTip messages and apply the result on existing calendars, + or automatically generate invites/replies/cancellations based on changes that + a user made on objects. +* #75, #58, #18: Fixes related to overriding the first event in recurrences. +* Added: VCalendar::getBaseComponent to find the 'master' component in a + calendar. +* #51: Support for iterating RDATE properties. +* Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are + excluded events with wrong time + + +3.2.4 (2014-07-14) +------------------ + +* Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into + consideration when converting between vCard 3 and 4. +* Fixed: Issue #96. Some support for Yahoo's broken vcards. +* Fixed: PHP 5.3 support was broken in the cli tool. + + +3.2.3 (2014-06-12) +------------------ + +* Validator now checks if DUE and DTSTART are of the same type in VTODO, and + ensures that DUE is always after DTSTART. +* Removed documentation from source repository, to http://sabre.io/vobject/ +* Expanded the vobject cli tool validation output to make it easier to find + issues. +* Fixed: vobject repair. It was not working for iCalendar objects. + + +3.2.2 (2014-05-07) +------------------ + +* Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying + slightly changed which caused the test to fail. + + +3.2.1 (2014-05-03) +------------------ + +* Minor tweak to make the unittests run with the latest hhvm on travis. +* Updated timezone definitions. +* Updated copyright links to point to http://sabre.io/ + + +3.2.0 (2014-04-02) +------------------ + +* Now hhvm compatible! +* The validator can now detect a _lot_ more problems. Many rules for both + iCalendar and vCard were added. +* Added: bin/generate_vcards, a utility to generate random vcards for testing + purposes. Patches are welcome to add more data. +* Updated: Windows timezone mapping to latest version from unicode.org +* Changed: The timezone maps are now loaded in from external files, in + lib/Sabre/VObject/timezonedata. +* Added: Fixing badly encoded URL's from google contacts vcards. +* Fixed: Issue #68. Couldn't decode properties ending in a colon. +* Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL + clause. +* Fixed: Issue #67. BYMONTH limit on DAILY recurrences. +* Fixed: Issue #26. Return a more descriptive error when coming across broken + BYDAY rules. +* Fixed: Issue #28. Incorrect timezone detection for some timezones. +* Fixed: Issue #70. Casting a parameter with a null value to string would fail. +* Added: Support for rfc6715 and rfc6474. +* Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property. +* Added: UUIDUtil, for easily creating unique identifiers. +* Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime. +* Fixed: Issue #86. Don't go into an infinite loop when php errors are + disabled and an invalid file is read. + + +3.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +3.1.3 (2013-10-02) +------------------ + +* Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue + #56. +* Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than + one newline. Thanks @Vedmak for the patch. +* Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to + be inserted. +* Added: VCardConverter removed properties that are no longer supported in vCard + 4.0. +* Added: vCards with a minimum number of values (such as N), but don't have that + many, are now automatically padded with empty components. +* Added: The vCard validator now also checks for a minimum number of components, + and has the ability to repair these. +* Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard + 3.0 or 4.0. +* Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level + VCalendar and VCard components. +* Fixed: Issue 62: Parsing iCalendar parameters with no value. +* Added: --forgiving option to vobject utility. +* Fixed: Compound properties such as ADR were not correctly split up in vCard + 2.1 quoted printable-encoded properties. +* Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks + @DominikTo for the patch. + + +3.1.2 (2013-08-13) +------------------ + +* Fixed: Setting correct property group on VCard conversion + + +3.1.1 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +3.1.0 (2013-07-27) +------------------ + +* Added: bad-ass new cli debugging utility (in bin/vobject). +* Added: jCal and jCard parser. +* Fixed: URI properties should not escape ; and ,. +* Fixed: VCard 4 documents now correctly use URI as a default value-type for + PHOTO and others. BINARY no longer exists in vCard 4. +* Added: Utility to convert between 2.1, 3.0 and 4.0 vCards. +* Added: You can now add() multiple parameters to a property in one call. +* Added: Parameter::has() for easily checking if a parameter value exists. +* Added: VCard::preferred() to find a preferred email, phone number, etc for a + contact. +* Changed: All $duration properties are now public. +* Added: A few validators for iCalendar documents. +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. +* Added: getDuration for DURATION values such as TRIGGER. Thanks to + @SimonSimCity. +* Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's + more than 1. Thanks to @Vedmak. + + +3.0.0 (2013-06-21) +------------------ + +* Fixed: includes.php file was still broken. Our tool to generate it had some + bugs. + + +3.0.0-beta4 (2013-06-21) +------------------------ + +* Fixed: includes.php was no longer up to date. + + +3.0.0-beta3 (2013-06-17) +------------------------ + +* Added: OPTION_FORGIVING now also allows slashes in property names. +* Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999 +* Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with + a missing base event. +* Fixed: jCard encoding of TIME properties. +* Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values. + + +3.0.0-beta2 (2013-06-10) +------------------------ + +* Fixed: Corrected includes.php file. +* Fixed: vCard date-time parser supported extended-format dates as well. +* Changed: Properties have been moved to an ICalendar or VCard directory. +* Fixed: Couldn't parse vCard 3 extended format dates and times. +* Fixed: Couldn't export jCard DATE values correctly. +* Fixed: Recursive loop in ICalendar\DateTime property. + + +3.0.0-beta1 (2013-06-07) +------------------------ + +* Added: jsonSerialize() for creating jCal and jCard documents. +* Added: helper method to parse vCard dates and times. +* Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP, + DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties. +* Removed: CommaSeparatedText property. Now included into Text. +* Fixed: Multiple parameters with the same name are now correctly encoded. +* Fixed: Parameter values containing a comma are now enclosed in double-quotes. +* Fixed: Iterating parameter values should now fully work as expected. +* Fixed: Support for vCard 2.1 nameless parameters. +* Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified + class names, so they are actually overridable. +* Fixed: Updating DATE-TIME to DATE values now behaves like expected. + + +3.0.0-alpha4 (2013-05-31) +------------------------- + +* Added: It's now possible to send parser options to the splitter classes. +* Added: A few tweaks to improve component and property creation. + + +3.0.0-alpha3 (2013-05-13) +------------------------- + +* Changed: propertyMap, valueMap and componentMap are now static properties. +* Changed: Component::remove() will throw an exception when trying to a node + that's not a child of said component. +* Added: Splitter objects are now faster, line numbers are accurately reported + and use less memory. +* Added: MimeDir parser can now continue parsing with the same stream buffer. +* Fixed: vobjectvalidate.php is operational again. +* Fixed: \r is properly stripped in text values. +* Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for + vCards 2.1. +* Fixed: Parser assumes vCard 2.1, if no version was supplied. + + +3.0.0-alpha2 (2013-05-22) +------------------------- + +* Fixed: vCard URL properties were referencing a non-existant class. + + +3.0.0-alpha1 (2013-05-21) +------------------------- + +* Fixed: Now correctly dealing with escaping of properties. This solves the + problem with double-backslashes where they don't belong. +* Added: Easy support for properties with more than one value, using setParts + and getParts. +* Added: Support for broken 2.1 vCards produced by microsoft. +* Added: Automatically decoding quoted-printable values. +* Added: Automatically decoding base64 values. +* Added: Decoding RFC6868 parameter values (uses ^ as an escape character). +* Added: Fancy new MimeDir parser that can also parse streams. +* Added: Automatically mapping many, many properties to a property-class with + specialized API's. +* Added: remove() method for easily removing properties and sub-components + components. +* Changed: Components, Properties and Parameters can no longer be created with + Component::create, Property::create and Parameter::create. They must instead + be created through the root component. (A VCalendar or VCard object). +* Changed: API for DateTime properties has slightly changed. +* Changed: the ->value property is now protected everywhere. Use getParts() and + getValue() instead. +* BC Break: No support for mac newlines (\r). Never came across these anyway. +* Added: add() method to the Property class. +* Added: It's now possible to easy set multi-value properties as arrays. +* Added: When setting date-time properties you can just pass PHP's DateTime + object. +* Added: New components automatically get a bunch of default properties, such as + VERSION and CALSCALE. +* Added: You can add new sub-components much quicker with the magic setters, and + add() method. + + +2.1.7 (2015-01-21) +------------------ + +* Fixed: Issue #94, a workaround for bad escaping of ; and , in compound + properties. It's not a full solution, but it's an improvement for those + stuck in the 2.1 versions. + + +2.1.6 (2014-12-10) +------------------ + +* Fixed: Minor change to make sure that unittests succeed on every PHP version. + + +2.1.5 (2014-06-03) +------------------ + +* Fixed: #94: Better parameter escaping. +* Changed: Documentation cleanups. + + +2.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +2.1.3 (2013-10-02) +------------------ + +* Fixed: Issue #55. \r must be stripped from property values. +* Fixed: Issue #65. Putting quotes around parameter values that contain a colon. + + +2.1.2 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +2.1.1 (2013-07-27) +------------------ + +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. + + +2.1.0 (2013-06-17) +------------------ + +* This version is fully backwards compatible with 2.0.\*. However, it contains a + few new API's that mimic the VObject 3 API. This allows it to be used a + 'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and + 1.8 can run with both the 2 and 3 versions of this library. +* Added: Property\DateTime::hasTime(). +* Added: Property\MultiDateTime::hasTime(). +* Added: Property::getValue(). +* Added: Document class. +* Added: Document::createComponent and Document::createProperty. +* Added: Parameter::getValue(). + + +2.0.7 (2013-03-05) +------------------ + +* Fixed: Microsoft re-uses their magic numbers for different timezones, + specifically id 2 for both Sarajevo and Lisbon). A workaround was added to + deal with this. + + +2.0.6 (2013-02-17) +------------------ + +* Fixed: The reader now properly parses parameters without a value. + + +2.0.5 (2012-11-05) +------------------ + +* Fixed: The FreeBusyGenerator is now properly using the factory methods for + creation of components and properties. + + +2.0.4 (2012-11-02) +------------------ + +* Added: Known Lotus Notes / Domino timezone id's. + + +2.0.3 (2012-10-29) +------------------ + +* Added: Support for 'GMT+????' format in TZID's. +* Added: Support for formats like SystemV/EST5EDT in TZID's. +* Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART. +* Added: Support for BYHOUR in FREQ=DAILY (@hollodk). +* Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY. + + +2.0.2 (2012-10-06) +------------------ + +* Added: includes.php file, to load the entire library in one go. +* Fixed: A problem with determining alarm triggers for TODO's. + + +2.0.1 (2012-09-22) +------------------ + +* Removed: Element class. It wasn't used. +* Added: Basic validation and repair methods for broken input data. +* Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was + specified. +* Added: A cli script that can validate and automatically repair vcards and + iCalendar objects. +* Added: A new 'Compound' property, that can automatically split up parts for + properties such as N, ADR, ORG and CATEGORIES. +* Added: Splitter classes, that can split up large objects (such as exports) + into individual objects (thanks @DominikTO and @armin-hackmann). +* Added: VFREEBUSY component, which allows easily checking wether timeslots are + available. +* Added: The Reader class now has a 'FORGIVING' option, which allows it to parse + properties with incorrect characters in the name (at this time, it just allows + underscores). +* Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard + any invalid lines. +* Fixed: A bug in Windows timezone-id mappings for times created in Greenlands + timezone (sorry Greenlanders! I do care!). +* Fixed: DTEND was not generated correctly for VFREEBUSY reports. +* Fixed: Parser is at least 25% faster with real-world data. + + +2.0.0 (2012-08-08) +------------------ + +* VObject is now a separate project from SabreDAV. See the SabreDAV changelog + for version information before 2.0. +* New: VObject library now uses PHP 5.3 namespaces. +* New: It's possible to specify lists of parameters when constructing + properties. +* New: made it easier to construct the FreeBusyGenerator. + +[iTip]: http://tools.ietf.org/html/rfc5546 diff --git a/vendor/sabre/vobject/LICENSE b/vendor/sabre/vobject/LICENSE new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sabre/vobject/README.md b/vendor/sabre/vobject/README.md new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/README.md @@ -0,0 +1,50 @@ +sabre/vobject +============= + +The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) +and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. + +The goal of the VObject library is to create a very complete library, with an easy to use API. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=master)](https://travis-ci.org/fruux/sabre-vobject) | +| 3.4 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.4)](https://travis-ci.org/fruux/sabre-vobject) | +| 3.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.1)](https://travis-ci.org/fruux/sabre-vobject) | +| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-vobject) | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-vobject) | + + +Installation +------------ + +VObject requires PHP 5.3, and should be installed using composer. +The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website). + +After that, just declare the vobject dependency as follows: + + "require" : { + "sabre/vobject" : "~3.4" + } + +Then, run `composer.phar update` and you should be good. + +Usage +----- + +* [3.x documentation](http://sabre.io/vobject/usage/) +* [2.x documentation](http://sabre.io/vobject/usage_2/) +* [Migrating from 2.x to 3.x](http://sabre.io/vobject/upgrade/) + +Support +------- + +Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/vendor/sabre/vobject/bin/bench.php b/vendor/sabre/vobject/bin/bench.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/bin/bench.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php +xpath('//mapZone') as $mapZone) { + + $from = (string)$mapZone['other']; + $to = (string)$mapZone['type']; + + list($to) = explode(' ', $to, 2); + + if (!isset($map[$from])) { + $map[$from] = $to; + } + +} + +ksort($map); +echo "Writing to: $outputFile\n"; + +$f = fopen($outputFile,'w'); +fwrite($f, " testdata.vcf + +HI; + + fwrite(STDERR, $help); + exit(2); +} + +$count = (int)$argv[1]; +if ($count < 1) { + fwrite(STDERR, "Count must be at least 1\n"); + exit(2); +} + +fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n"); +fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n"); + +/** + * The following list is just some random data we compiled from various + * sources online. + * + * Very little thought went into compiling this list, and certainly nothing + * political or ethical. + * + * We would _love_ more additions to this to add more variation to this list. + * + * Send us PR's and don't be shy adding your own first and last name for fun. + */ + +$sets = array( + "nl" => array( + "country" => "Netherlands", + "boys" => array( + "Anno", + "Bram", + "Daan", + "Evert", + "Finn", + "Jayden", + "Jens", + "Jesse", + "Levi", + "Lucas", + "Luuk", + "Milan", + "René", + "Sem", + "Sibrand", + "Willem", + ), + "girls" => array( + "Celia", + "Emma", + "Fenna", + "Geke", + "Inge", + "Julia", + "Lisa", + "Lotte", + "Mila", + "Sara", + "Sophie", + "Tess", + "Zoë", + ), + "last" => array( + "Bakker", + "Bos", + "De Boer", + "De Groot", + "De Jong", + "De Vries", + "Jansen", + "Janssen", + "Meyer", + "Mulder", + "Peters", + "Smit", + "Van Dijk", + "Van den Berg", + "Visser", + "Vos", + ), + ), + "us" => array( + "country" => "United States", + "boys" => array( + "Aiden", + "Alexander", + "Charles", + "David", + "Ethan", + "Jacob", + "James", + "Jayden", + "John", + "Joseph", + "Liam", + "Mason", + "Michael", + "Noah", + "Richard", + "Robert", + "Thomas", + "William", + ), + "girls" => array( + "Ava", + "Barbara", + "Chloe", + "Dorothy", + "Elizabeth", + "Emily", + "Emma", + "Isabella", + "Jennifer", + "Lily", + "Linda", + "Margaret", + "Maria", + "Mary", + "Mia", + "Olivia", + "Patricia", + "Roxy", + "Sophia", + "Susan", + "Zoe", + ), + "last" => array( + "Smith", + "Johnson", + "Williams", + "Jones", + "Brown", + "Davis", + "Miller", + "Wilson", + "Moore", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + "Harris", + "Martin", + "Thompson", + "Garcia", + "Martinez", + "Robinson", + ), + ), +); + +$current = 0; + +$r = function($arr) { + + return $arr[mt_rand(0,count($arr)-1)]; + +}; + +$bdayStart = strtotime('-85 years'); +$bdayEnd = strtotime('-20 years'); + +while($current < $count) { + + $current++; + fwrite(STDERR, "\033[100D$current/$count"); + + $country = array_rand($sets); + $gender = mt_rand(0,1)?'girls':'boys'; + + $vcard = new Component\VCard(array( + 'VERSION' => '4.0', + 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']), + 'UID' => UUIDUtil::getUUID(), + )); + + $bdayRatio = mt_rand(0,9); + + if($bdayRatio < 2) { + // 20% has a birthday property with a full date + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', $dt->format('Ymd')); + + } elseif ($bdayRatio < 3) { + // 10% we only know the month and date of + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', '--' . $dt->format('md')); + } + if ($result = $vcard->validate()) { + ob_start(); + echo "\nWe produced an invalid vcard somehow!\n"; + foreach($result as $message) { + echo " " . $message['message'] . "\n"; + } + fwrite(STDERR, ob_get_clean()); + } + echo $vcard->serialize(); + +} + +fwrite(STDERR,"\nDone.\n"); diff --git a/vendor/sabre/vobject/bin/generateicalendardata.php b/vendor/sabre/vobject/bin/generateicalendardata.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/bin/generateicalendardata.php @@ -0,0 +1,91 @@ +#!/usr/bin/env php +version = '2.0'; +$calendar->calscale = 'GREGORIAN'; + +$ii=0; + +while($ii < $events) { + + $ii++; + + $event = VObject\Component::create('VEVENT'); + $event->DTSTART = 'bla'; + $event->SUMMARY = 'Event #' . $ii; + $event->UID = md5(microtime(true)); + + $doctorRandom = mt_rand(1,1000); + + switch($doctorRandom) { + // All-day event + case 1 : + $event->DTEND = 'bla'; + $dtStart = clone $currentDate; + $dtEnd = clone $currentDate; + $dtEnd->modify('+' . mt_rand(1,3) . ' days'); + $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::DATE); + $event->DTEND->setDateTime($dtEnd, VObject\Property\DateTime::DATE); + break; + case 2 : + $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1,10); + // No break intentional + default : + $dtStart = clone $currentDate; + $dtStart->setTime(mt_rand(1,23), mt_rand(0,59), mt_rand(0,59)); + $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::UTC); + $event->DURATION = 'PT'.mt_rand(1,3).'H'; + break; + + } + + $calendar->add($event); + $currentDate->modify('+ ' . mt_rand(0,3) . ' days'); + +} +fwrite(STDERR, "Validating\n"); + +$result = $calendar->validate(); +if ($result) { + fwrite(STDERR, "Errors!\n"); + fwrite(STDERR, print_r($result,true)); + die(-1); +} + +fwrite(STDERR, "Serializing this beast\n"); + +echo $calendar->serialize(); + +fwrite(STDERR, "done.\n"); + diff --git a/vendor/sabre/vobject/bin/rrulebench.php b/vendor/sabre/vobject/bin/rrulebench.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/bin/rrulebench.php @@ -0,0 +1,32 @@ +parse->start(); + +echo "Parsing.\n"; +$vobj = Sabre\VObject\Reader::read(fopen($inputFile,'r')); + +$bench->parse->stop(); + +echo "Expanding.\n"; +$bench->expand->start(); + +$vobj->expand(new DateTime($startDate), new DateTime($endDate)); + +$bench->expand->stop(); + +echo $bench,"\n"; diff --git a/vendor/sabre/vobject/bin/vobject b/vendor/sabre/vobject/bin/vobject new file mode 100755 --- /dev/null +++ b/vendor/sabre/vobject/bin/vobject @@ -0,0 +1,27 @@ +#!/usr/bin/env php +main($argv)); + diff --git a/vendor/sabre/vobject/composer.json b/vendor/sabre/vobject/composer.json new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sabre/vobject", + "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "keywords" : [ "VObject", "iCalendar", "vCard", "jCard", "jCal" ], + "homepage" : "http://sabre.io/vobject/", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.3.1", + "ext-mbstring" : "*" + }, + "require-dev" : { + "phpunit/phpunit" : "*", + "squizlabs/php_codesniffer": "*" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + }, + { + "name" : "Dominik Tobschall", + "email" : "dominik@fruux.com", + "homepage" : "http://tobschall.de/", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-vobject" + }, + "autoload" : { + "psr-4" : { + "Sabre\\VObject\\" : "lib/" + } + }, + "bin" : [ + "bin/vobject", + "bin/generate_vcards" + ], + "extra" : { + "branch-alias" : { + "dev-master" : "3.2.x-dev" + } + }, + "config" : { + "bin-dir" : "bin" + } +} diff --git a/vendor/sabre/vobject/lib/Cli.php b/vendor/sabre/vobject/lib/Cli.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Cli.php @@ -0,0 +1,761 @@ +stderr) { + $this->stderr = fopen('php://stderr', 'w'); + } + if (!$this->stdout) { + $this->stdout = fopen('php://stdout', 'w'); + } + if (!$this->stdin) { + $this->stdin = fopen('php://stdin', 'r'); + } + + // @codeCoverageIgnoreEnd + + + try { + + list($options, $positional) = $this->parseArguments($argv); + + if (isset($options['q'])) { + $this->quiet = true; + } + $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION)); + + foreach($options as $name=>$value) { + + switch($name) { + + case 'q' : + // Already handled earlier. + break; + case 'h' : + case 'help' : + $this->showHelp(); + return 0; + break; + case 'format' : + switch($value) { + + // jcard/jcal documents + case 'jcard' : + case 'jcal' : + + // specific document versions + case 'vcard21' : + case 'vcard30' : + case 'vcard40' : + case 'icalendar20' : + + // specific formats + case 'json' : + case 'mimedir' : + + // icalendar/vcad + case 'icalendar' : + case 'vcard' : + $this->format = $value; + break; + + default : + throw new InvalidArgumentException('Unknown format: ' . $value); + + } + break; + case 'pretty' : + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $this->pretty = true; + } + break; + case 'forgiving' : + $this->forgiving = true; + break; + case 'inputformat' : + switch($value) { + // json formats + case 'jcard' : + case 'jcal' : + case 'json' : + $this->inputFormat = 'json'; + break; + + // mimedir formats + case 'mimedir' : + case 'icalendar' : + case 'vcard' : + case 'vcard21' : + case 'vcard30' : + case 'vcard40' : + case 'icalendar20' : + + $this->inputFormat = 'mimedir'; + break; + + default : + throw new InvalidArgumentException('Unknown format: ' . $value); + + } + break; + default : + throw new InvalidArgumentException('Unknown option: ' . $name); + + } + + } + + if (count($positional) === 0) { + $this->showHelp(); + return 1; + } + + if (count($positional) === 1) { + throw new InvalidArgumentException('Inputfile is a required argument'); + } + + if (count($positional) > 3) { + throw new InvalidArgumentException('Too many arguments'); + } + + if (!in_array($positional[0], array('validate','repair','convert','color'))) { + throw new InvalidArgumentException('Uknown command: ' . $positional[0]); + } + + } catch (InvalidArgumentException $e) { + $this->showHelp(); + $this->log('Error: ' . $e->getMessage(), 'red'); + return 1; + } + + $command = $positional[0]; + + $this->inputPath = $positional[1]; + $this->outputPath = isset($positional[2])?$positional[2]:'-'; + + if ($this->outputPath !== '-') { + $this->stdout = fopen($this->outputPath, 'w'); + } + + if (!$this->inputFormat) { + if (substr($this->inputPath, -5)==='.json') { + $this->inputFormat = 'json'; + } else { + $this->inputFormat = 'mimedir'; + } + } + if (!$this->format) { + if (substr($this->outputPath,-5)==='.json') { + $this->format = 'json'; + } else { + $this->format = 'mimedir'; + } + } + + + $realCode = 0; + + try { + + while($input = $this->readInput()) { + + $returnCode = $this->$command($input); + if ($returnCode!==0) $realCode = $returnCode; + + } + + } catch (EofException $e) { + // end of file + } catch (\Exception $e) { + $this->log('Error: ' . $e->getMessage(),'red'); + return 2; + } + + return $realCode; + + } + + /** + * Shows the help message. + * + * @return void + */ + protected function showHelp() { + + $this->log('Usage:', 'yellow'); + $this->log(" vobject [options] command [arguments]"); + $this->log(''); + $this->log('Options:', 'yellow'); + $this->log($this->colorize('green', ' -q ') . "Don't output anything."); + $this->log($this->colorize('green', ' -help -h ') . "Display this help message."); + $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,"); + $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict."); + $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir."); + $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it"); + $this->log(" must be specified here."); + // Only PHP 5.4 and up + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $this->log($this->colorize('green', ' --pretty ') . "json pretty-print."); + } + $this->log(''); + $this->log('Commands:', 'yellow'); + $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.'); + $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.'); + $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.'); + $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.'); + $this->log( + <<log('Examples:', 'yellow'); + $this->log(' vobject convert contact.vcf contact.json'); + $this->log(' vobject convert --format=vcard40 old.vcf new.vcf'); + $this->log(' vobject convert --inputformat=json --format=mimedir - -'); + $this->log(' vobject color calendar.ics'); + $this->log(''); + $this->log('https://github.com/fruux/sabre-vobject','purple'); + + } + + /** + * Validates a VObject file + * + * @param Component $vObj + * @return int + */ + protected function validate($vObj) { + + $returnCode = 0; + + switch($vObj->name) { + case 'VCALENDAR' : + $this->log("iCalendar: " . (string)$vObj->VERSION); + break; + case 'VCARD' : + $this->log("vCard: " . (string)$vObj->VERSION); + break; + } + + $warnings = $vObj->validate(); + if (!count($warnings)) { + $this->log(" No warnings!"); + } else { + + $levels = array( + 1 => 'REPAIRED', + 2 => 'WARNING', + 3 => 'ERROR', + ); + $returnCode = 2; + foreach($warnings as $warn) { + + $extra = ''; + if ($warn['node'] instanceof Property) { + $extra = ' (property: "' . $warn['node']->name . '")'; + } + $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); + + } + + } + + return $returnCode; + + } + + /** + * Repairs a VObject file + * + * @param Component $vObj + * @return int + */ + protected function repair($vObj) { + + $returnCode = 0; + + switch($vObj->name) { + case 'VCALENDAR' : + $this->log("iCalendar: " . (string)$vObj->VERSION); + break; + case 'VCARD' : + $this->log("vCard: " . (string)$vObj->VERSION); + break; + } + + $warnings = $vObj->validate(Node::REPAIR); + if (!count($warnings)) { + $this->log(" No warnings!"); + } else { + + $levels = array( + 1 => 'REPAIRED', + 2 => 'WARNING', + 3 => 'ERROR', + ); + $returnCode = 2; + foreach($warnings as $warn) { + + $extra = ''; + if ($warn['node'] instanceof Property) { + $extra = ' (property: "' . $warn['node']->name . '")'; + } + $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); + + } + + } + fwrite($this->stdout, $vObj->serialize()); + + return $returnCode; + + } + + /** + * Converts a vObject file to a new format. + * + * @param Component $vObj + * @return int + */ + protected function convert($vObj) { + + $json = false; + $convertVersion = null; + $forceInput = null; + + switch($this->format) { + case 'json' : + $json = true; + if ($vObj->name === 'VCARD') { + $convertVersion = Document::VCARD40; + } + break; + case 'jcard' : + $json = true; + $forceInput = 'VCARD'; + $convertVersion = Document::VCARD40; + break; + case 'jcal' : + $json = true; + $forceInput = 'VCALENDAR'; + break; + case 'mimedir' : + case 'icalendar' : + case 'icalendar20' : + case 'vcard' : + break; + case 'vcard21' : + $convertVersion = Document::VCARD21; + break; + case 'vcard30' : + $convertVersion = Document::VCARD30; + break; + case 'vcard40' : + $convertVersion = Document::VCARD40; + break; + + } + + if ($forceInput && $vObj->name !== $forceInput) { + throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format); + } + if ($convertVersion) { + $vObj = $vObj->convert($convertVersion); + } + if ($json) { + $jsonOptions = 0; + if ($this->pretty) { + $jsonOptions = JSON_PRETTY_PRINT; + } + fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions)); + } else { + fwrite($this->stdout, $vObj->serialize()); + } + + return 0; + + } + + /** + * Colorizes a file + * + * @param Component $vObj + * @return int + */ + protected function color($vObj) { + + fwrite($this->stdout, $this->serializeComponent($vObj)); + + } + + /** + * Returns an ansi color string for a color name. + * + * @param string $color + * @return string + */ + protected function colorize($color, $str, $resetTo = 'default') { + + $colors = array( + 'cyan' => '1;36', + 'red' => '1;31', + 'yellow' => '1;33', + 'blue' => '0;34', + 'green' => '0;32', + 'default' => '0', + 'purple' => '0;35', + ); + return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m"; + + } + + /** + * Writes out a string in specific color. + * + * @param string $color + * @param string $str + * @return void + */ + protected function cWrite($color, $str) { + + fwrite($this->stdout, $this->colorize($color, $str)); + + } + + protected function serializeComponent(Component $vObj) { + + $this->cWrite('cyan', 'BEGIN'); + $this->cWrite('red', ':'); + $this->cWrite('yellow', $vObj->name . "\n"); + + /** + * Gives a component a 'score' for sorting purposes. + * + * This is solely used by the childrenSort method. + * + * A higher score means the item will be lower in the list. + * To avoid score collisions, each "score category" has a reasonable + * space to accomodate elements. The $key is added to the $score to + * preserve the original relative order of elements. + * + * @param int $key + * @param array $array + * @return int + */ + $sortScore = function($key, $array) { + + if ($array[$key] instanceof Component) { + + // We want to encode VTIMEZONE first, this is a personal + // preference. + if ($array[$key]->name === 'VTIMEZONE') { + $score=300000000; + return $score+$key; + } else { + $score=400000000; + return $score+$key; + } + } else { + // Properties get encoded first + // VCARD version 4.0 wants the VERSION property to appear first + if ($array[$key] instanceof Property) { + if ($array[$key]->name === 'VERSION') { + $score=100000000; + return $score+$key; + } else { + // All other properties + $score=200000000; + return $score+$key; + } + } + } + + }; + + $tmp = $vObj->children; + uksort( + $vObj->children, + function($a, $b) use ($sortScore, $tmp) { + + $sA = $sortScore($a, $tmp); + $sB = $sortScore($b, $tmp); + + return $sA - $sB; + + } + ); + + foreach($vObj->children as $child) { + if ($child instanceof Component) { + $this->serializeComponent($child); + } else { + $this->serializeProperty($child); + } + } + + $this->cWrite('cyan', 'END'); + $this->cWrite('red', ':'); + $this->cWrite('yellow', $vObj->name . "\n"); + + } + + /** + * Colorizes a property. + * + * @param Property $property + * @return void + */ + protected function serializeProperty(Property $property) { + + if ($property->group) { + $this->cWrite('default', $property->group); + $this->cWrite('red', '.'); + } + + $str = ''; + $this->cWrite('yellow', $property->name); + + foreach($property->parameters as $param) { + + $this->cWrite('red',';'); + $this->cWrite('blue', $param->serialize()); + + } + $this->cWrite('red',':'); + + if ($property instanceof Property\Binary) { + + $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)'); + + } else { + + $parts = $property->getParts(); + $first1 = true; + // Looping through property values + foreach($parts as $part) { + if ($first1) { + $first1 = false; + } else { + $this->cWrite('red', $property->delimiter); + } + $first2 = true; + // Looping through property sub-values + foreach((array)$part as $subPart) { + if ($first2) { + $first2 = false; + } else { + // The sub-value delimiter is always comma + $this->cWrite('red', ','); + } + + $subPart = strtr( + $subPart, + array( + '\\' => $this->colorize('purple', '\\\\', 'green'), + ';' => $this->colorize('purple', '\;', 'green'), + ',' => $this->colorize('purple', '\,', 'green'), + "\n" => $this->colorize('purple', "\\n\n\t", 'green'), + "\r" => "", + ) + ); + + $this->cWrite('green', $subPart); + } + } + + } + $this->cWrite("default", "\n"); + + } + + /** + * Parses the list of arguments. + * + * @param array $argv + * @return void + */ + protected function parseArguments(array $argv) { + + $positional = array(); + $options = array(); + + for($ii=0; $ii < count($argv); $ii++) { + + // Skipping the first argument. + if ($ii===0) continue; + + $v = $argv[$ii]; + + if (substr($v,0,2)==='--') { + // This is a long-form option. + $optionName = substr($v,2); + $optionValue = true; + if (strpos($optionName,'=')) { + list($optionName, $optionValue) = explode('=', $optionName); + } + $options[$optionName] = $optionValue; + } elseif (substr($v,0,1) === '-' && strlen($v)>1) { + // This is a short-form option. + foreach(str_split(substr($v,1)) as $option) { + $options[$option] = true; + } + + } else { + + $positional[] = $v; + + } + + } + + return array($options, $positional); + + } + + protected $parser; + + /** + * Reads the input file + * + * @return Component + */ + protected function readInput() { + + if (!$this->parser) { + if ($this->inputPath!=='-') { + $this->stdin = fopen($this->inputPath,'r'); + } + + if ($this->inputFormat === 'mimedir') { + $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0)); + } else { + $this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0)); + } + } + + return $this->parser->parse(); + + } + + /** + * Sends a message to STDERR. + * + * @param string $msg + * @return void + */ + protected function log($msg, $color = 'default') { + + if (!$this->quiet) { + if ($color!=='default') { + $msg = $this->colorize($color, $msg); + } + fwrite($this->stderr, $msg . "\n"); + } + + } + +} diff --git a/vendor/sabre/vobject/lib/Component.php b/vendor/sabre/vobject/lib/Component.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component.php @@ -0,0 +1,595 @@ +value syntax, in which case + * properties will automatically be created, or you can just pass a list of + * Component and Property object. + * + * By default, a set of sensible values will be added to the component. For + * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To + * ensure that this does not happen, set $defaults to false. + * + * @param Document $root + * @param string $name such as VCALENDAR, VEVENT. + * @param array $children + * @param bool $defaults + * @return void + */ + function __construct(Document $root, $name, array $children = array(), $defaults = true) { + + $this->name = strtoupper($name); + $this->root = $root; + + if ($defaults) { + // This is a terribly convoluted way to do this, but this ensures + // that the order of properties as they are specified in both + // defaults and the childrens list, are inserted in the object in a + // natural way. + $list = $this->getDefaults(); + $nodes = array(); + foreach($children as $key=>$value) { + if ($value instanceof Node) { + if (isset($list[$value->name])) { + unset($list[$value->name]); + } + $nodes[] = $value; + } else { + $list[$key] = $value; + } + } + foreach($list as $key=>$value) { + $this->add($key, $value); + } + foreach($nodes as $node) { + $this->add($node); + } + } else { + foreach($children as $k=>$child) { + if ($child instanceof Node) { + + // Component or Property + $this->add($child); + } else { + + // Property key=>value + $this->add($k, $child); + } + } + } + + } + + /** + * Adds a new property or component, and returns the new item. + * + * This method has 3 possible signatures: + * + * add(Component $comp) // Adds a new component + * add(Property $prop) // Adds a new property + * add($name, $value, array $parameters = array()) // Adds a new property + * add($name, array $children = array()) // Adds a new component + * by name. + * + * @return Node + */ + function add($a1, $a2 = null, $a3 = null) { + + if ($a1 instanceof Node) { + if (!is_null($a2)) { + throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node'); + } + $a1->parent = $this; + $this->children[] = $a1; + + return $a1; + + } elseif(is_string($a1)) { + + $item = $this->root->create($a1, $a2, $a3); + $item->parent = $this; + $this->children[] = $item; + + return $item; + + } else { + + throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string'); + + } + + } + + /** + * This method removes a component or property from this component. + * + * You can either specify the item by name (like DTSTART), in which case + * all properties/components with that name will be removed, or you can + * pass an instance of a property or component, in which case only that + * exact item will be removed. + * + * The removed item will be returned. In case there were more than 1 items + * removed, only the last one will be returned. + * + * @param mixed $item + * @return void + */ + function remove($item) { + + if (is_string($item)) { + $children = $this->select($item); + foreach($children as $k=>$child) { + unset($this->children[$k]); + } + return $child; + } else { + foreach($this->children as $k => $child) { + if ($child===$item) { + unset($this->children[$k]); + return $child; + } + } + + throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); + + } + + } + + /** + * Returns an iterable list of children + * + * @return array + */ + function children() { + + return $this->children; + + } + + /** + * This method only returns a list of sub-components. Properties are + * ignored. + * + * @return array + */ + function getComponents() { + + $result = array(); + foreach($this->children as $child) { + if ($child instanceof Component) { + $result[] = $child; + } + } + + return $result; + + } + + /** + * Returns an array with elements that match the specified name. + * + * This function is also aware of MIME-Directory groups (as they appear in + * vcards). This means that if a property is grouped as "HOME.EMAIL", it + * will also be returned when searching for just "EMAIL". If you want to + * search for a property in a specific group, you can select on the entire + * string ("HOME.EMAIL"). If you want to search on a specific property that + * has not been assigned a group, specify ".EMAIL". + * + * Keys are retained from the 'children' array, which may be confusing in + * certain cases. + * + * @param string $name + * @return array + */ + function select($name) { + + $group = null; + $name = strtoupper($name); + if (strpos($name,'.')!==false) { + list($group,$name) = explode('.', $name, 2); + } + + $result = array(); + foreach($this->children as $key=>$child) { + + if ( + ( + strtoupper($child->name) === $name + && (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group)) + ) + || + ( + $name === '' && $child instanceof Property && strtoupper($child->group) === $group + ) + ) { + + $result[$key] = $child; + + } + } + + reset($result); + return $result; + + } + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + function serialize() { + + $str = "BEGIN:" . $this->name . "\r\n"; + + /** + * Gives a component a 'score' for sorting purposes. + * + * This is solely used by the childrenSort method. + * + * A higher score means the item will be lower in the list. + * To avoid score collisions, each "score category" has a reasonable + * space to accomodate elements. The $key is added to the $score to + * preserve the original relative order of elements. + * + * @param int $key + * @param array $array + * @return int + */ + $sortScore = function($key, $array) { + + if ($array[$key] instanceof Component) { + + // We want to encode VTIMEZONE first, this is a personal + // preference. + if ($array[$key]->name === 'VTIMEZONE') { + $score=300000000; + return $score+$key; + } else { + $score=400000000; + return $score+$key; + } + } else { + // Properties get encoded first + // VCARD version 4.0 wants the VERSION property to appear first + if ($array[$key] instanceof Property) { + if ($array[$key]->name === 'VERSION') { + $score=100000000; + return $score+$key; + } else { + // All other properties + $score=200000000; + return $score+$key; + } + } + } + + }; + + $tmp = $this->children; + uksort( + $this->children, + function($a, $b) use ($sortScore, $tmp) { + + $sA = $sortScore($a, $tmp); + $sB = $sortScore($b, $tmp); + + return $sA - $sB; + + } + ); + + foreach($this->children as $child) $str.=$child->serialize(); + $str.= "END:" . $this->name . "\r\n"; + + return $str; + + } + + /** + * This method returns an array, with the representation as it should be + * encoded in json. This is used to create jCard or jCal documents. + * + * @return array + */ + function jsonSerialize() { + + $components = array(); + $properties = array(); + + foreach($this->children as $child) { + if ($child instanceof Component) { + $components[] = $child->jsonSerialize(); + } else { + $properties[] = $child->jsonSerialize(); + } + } + + return array( + strtolower($this->name), + $properties, + $components + ); + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return array(); + + } + + /* Magic property accessors {{{ */ + + /** + * Using 'get' you will either get a property or component. + * + * If there were no child-elements found with the specified name, + * null is returned. + * + * To use this, this may look something like this: + * + * $event = $calendar->VEVENT; + * + * @param string $name + * @return Property + */ + function __get($name) { + + $matches = $this->select($name); + if (count($matches)===0) { + return null; + } else { + $firstMatch = current($matches); + /** @var $firstMatch Property */ + $firstMatch->setIterator(new ElementList(array_values($matches))); + return $firstMatch; + } + + } + + /** + * This method checks if a sub-element with the specified name exists. + * + * @param string $name + * @return bool + */ + function __isset($name) { + + $matches = $this->select($name); + return count($matches)>0; + + } + + /** + * Using the setter method you can add properties or subcomponents + * + * You can either pass a Component, Property + * object, or a string to automatically create a Property. + * + * If the item already exists, it will be removed. If you want to add + * a new item with the same name, always use the add() method. + * + * @param string $name + * @param mixed $value + * @return void + */ + function __set($name, $value) { + + $matches = $this->select($name); + $overWrite = count($matches)?key($matches):null; + + if ($value instanceof Component || $value instanceof Property) { + $value->parent = $this; + if (!is_null($overWrite)) { + $this->children[$overWrite] = $value; + } else { + $this->children[] = $value; + } + } else { + $property = $this->root->create($name,$value); + $property->parent = $this; + if (!is_null($overWrite)) { + $this->children[$overWrite] = $property; + } else { + $this->children[] = $property; + } + } + } + + /** + * Removes all properties and components within this component with the + * specified name. + * + * @param string $name + * @return void + */ + function __unset($name) { + + $matches = $this->select($name); + foreach($matches as $k=>$child) { + + unset($this->children[$k]); + $child->parent = null; + + } + + } + + /* }}} */ + + /** + * This method is automatically called when the object is cloned. + * Specifically, this will ensure all child elements are also cloned. + * + * @return void + */ + function __clone() { + + foreach($this->children as $key=>$child) { + $this->children[$key] = clone $child; + $this->children[$key]->parent = $this; + } + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * It is also possible to specify defaults and severity levels for + * violating the rule. + * + * See the VEVENT implementation for getValidationRules for a more complex + * example. + * + * @var array + */ + function getValidationRules() { + + return array(); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. + * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on). + * 2 - A warning. + * 3 - An error. + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $rules = $this->getValidationRules(); + $defaults = $this->getDefaults(); + + $propertyCounters = array(); + + $messages = array(); + + foreach($this->children as $child) { + $name = strtoupper($child->name); + if (!isset($propertyCounters[$name])) { + $propertyCounters[$name] = 1; + } else { + $propertyCounters[$name]++; + } + $messages = array_merge($messages, $child->validate($options)); + } + + foreach($rules as $propName => $rule) { + + switch($rule) { + case '0' : + if (isset($propertyCounters[$propName])) { + $messages[] = array( + 'level' => 3, + 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component', + 'node' => $this, + ); + } + break; + case '1' : + if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName]!==1) { + $repaired = false; + if ($options & self::REPAIR && isset($defaults[$propName])) { + $this->add($propName, $defaults[$propName]); + } + $messages[] = array( + 'level' => $repaired?1:3, + 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component', + 'node' => $this, + ); + } + break; + case '+' : + if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) { + $messages[] = array( + 'level' => 3, + 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component', + 'node' => $this, + ); + } + break; + case '*' : + break; + case '?' : + if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) { + $messages[] = array( + 'level' => 3, + 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component', + 'node' => $this, + ); + } + break; + + } + + } + return $messages; + + } + +} diff --git a/vendor/sabre/vobject/lib/Component/Available.php b/vendor/sabre/vobject/lib/Component/Available.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/Available.php @@ -0,0 +1,108 @@ + 1, + 'DTSTART' => 1, + 'DTSTAMP' => 1, + + 'DTEND' => '?', + 'DURATION' => '?', + + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'LAST-MODIFIED' => '?', + 'RECURRENCE-ID' => '?', + 'RRULE' => '?', + 'SUMMARY' => '?', + + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'RDATE' => '*', + + 'AVAILABLE' => '*', + ); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. + * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on). + * 2 - A warning. + * 3 - An error. + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $result = parent::validate($options); + + if (isset($this->DTEND) && isset($this->DURATION)) { + $result[] = array( + 'level' => 3, + 'message' => 'DTEND and DURATION cannot both be present', + 'node' => $this + ); + } + + if (isset($this->DURATION) && !isset($this->DTSTART)) { + $result[] = array( + 'level' => 3, + 'message' => 'DURATION must be declared with a DTSTART.', + 'node' => $this + ); + } + + return $result; + + } +} diff --git a/vendor/sabre/vobject/lib/Component/VAlarm.php b/vendor/sabre/vobject/lib/Component/VAlarm.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VAlarm.php @@ -0,0 +1,137 @@ +TRIGGER; + if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') { + $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); + $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START'; + + $parentComponent = $this->parent; + if ($related === 'START') { + + if ($parentComponent->name === 'VTODO') { + $propName = 'DUE'; + } else { + $propName = 'DTSTART'; + } + + $effectiveTrigger = clone $parentComponent->$propName->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } else { + if ($parentComponent->name === 'VTODO') { + $endProp = 'DUE'; + } elseif ($parentComponent->name === 'VEVENT') { + $endProp = 'DTEND'; + } else { + throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); + } + + if (isset($parentComponent->$endProp)) { + $effectiveTrigger = clone $parentComponent->$endProp->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } elseif (isset($parentComponent->DURATION)) { + $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); + $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION); + $effectiveTrigger->add($duration); + $effectiveTrigger->add($triggerDuration); + } else { + $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } + } + } else { + $effectiveTrigger = $trigger->getDateTime(); + } + return $effectiveTrigger; + + } + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param \DateTime $start + * @param \DateTime $end + * @return bool + */ + public function isInTimeRange(\DateTime $start, \DateTime $end) { + + $effectiveTrigger = $this->getEffectiveTriggerTime(); + + if (isset($this->DURATION)) { + $duration = VObject\DateTimeParser::parseDuration($this->DURATION); + $repeat = (string)$this->repeat; + if (!$repeat) { + $repeat = 1; + } + + $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat); + + foreach($period as $occurrence) { + + if ($start <= $occurrence && $end > $occurrence) { + return true; + } + } + return false; + } else { + return ($start <= $effectiveTrigger && $end > $effectiveTrigger); + } + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + public function getValidationRules() { + + return array( + 'ACTION' => 1, + 'TRIGGER' => 1, + + 'DURATION' => '?', + 'REPEAT' => '?', + + 'ATTACH' => '?', + ); + + } + +} diff --git a/vendor/sabre/vobject/lib/Component/VAvailability.php b/vendor/sabre/vobject/lib/Component/VAvailability.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VAvailability.php @@ -0,0 +1,99 @@ + 1, + 'DTSTAMP' => 1, + + 'BUSYTYPE' => '?', + 'CLASS' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'DTSTART' => '?', + 'LAST-MODIFIED' => '?', + 'ORGANIZER' => '?', + 'PRIORITY' => '?', + 'SEQUENCE' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + 'DTEND' => '?', + 'DURATION' => '?', + + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + ); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. + * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on). + * 2 - A warning. + * 3 - An error. + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $result = parent::validate($options); + + if (isset($this->DTEND) && isset($this->DURATION)) { + $result[] = array( + 'level' => 3, + 'message' => 'DTEND and DURATION cannot both be present', + 'node' => $this + ); + } + + return $result; + + } +} diff --git a/vendor/sabre/vobject/lib/Component/VCalendar.php b/vendor/sabre/vobject/lib/Component/VCalendar.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VCalendar.php @@ -0,0 +1,526 @@ + 'Sabre\\VObject\\Component\\VAlarm', + 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', + 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', + 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability', + 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available', + 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', + 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', + 'VTODO' => 'Sabre\\VObject\\Component\\VTodo', + ); + + /** + * List of value-types, and which classes they map to. + * + * @var array + */ + static $valueMap = array( + 'BINARY' => 'Sabre\\VObject\\Property\\Binary', + 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', + 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', + 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', + 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', + 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', + 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', + 'TEXT' => 'Sabre\\VObject\\Property\\Text', + 'TIME' => 'Sabre\\VObject\\Property\\Time', + 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. + 'URI' => 'Sabre\\VObject\\Property\\Uri', + 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + ); + + /** + * List of properties, and which classes they map to. + * + * @var array + */ + static $propertyMap = array( + // Calendar properties + 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', + 'METHOD' => 'Sabre\\VObject\\Property\\FlatText', + 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', + 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', + + // Component properties + 'ATTACH' => 'Sabre\\VObject\\Property\\Uri', + 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', + 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', + 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', + 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', + 'GEO' => 'Sabre\\VObject\\Property\\FloatValue', + 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', + 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue', + 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue', + 'RESOURCES' => 'Sabre\\VObject\\Property\\Text', + 'STATUS' => 'Sabre\\VObject\\Property\\FlatText', + 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', + + // Date and Time Component Properties + 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', + 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', + + // Time Zone Component Properties + 'TZID' => 'Sabre\\VObject\\Property\\FlatText', + 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', + 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', + 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', + 'TZURL' => 'Sabre\\VObject\\Property\\Uri', + + // Relationship Component Properties + 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', + 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', + 'URL' => 'Sabre\\VObject\\Property\\Uri', + 'UID' => 'Sabre\\VObject\\Property\\FlatText', + + // Recurrence Component Properties + 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', + 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 + + // Alarm Component Properties + 'ACTION' => 'Sabre\\VObject\\Property\\FlatText', + 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue', + 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + + // Change Management Component Properties + 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue', + + // Request Status + 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', + + // Additions from draft-daboo-valarm-extensions-04 + 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', + 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', + 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', + + // Additions from draft-daboo-calendar-availability-05 + 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text', + + ); + + /** + * Returns the current document type. + * + * @return void + */ + function getDocumentType() { + + return self::ICALENDAR20; + + } + + /** + * Returns a list of all 'base components'. For instance, if an Event has + * a recurrence rule, and one instance is overridden, the overridden event + * will have the same UID, but will be excluded from this list. + * + * VTIMEZONE components will always be excluded. + * + * @param string $componentName filter by component name + * @return VObject\Component[] + */ + function getBaseComponents($componentName = null) { + + $components = array(); + foreach($this->children as $component) { + + if (!$component instanceof VObject\Component) + continue; + + if (isset($component->{'RECURRENCE-ID'})) + continue; + + if ($componentName && $component->name !== strtoupper($componentName)) + continue; + + if ($component->name === 'VTIMEZONE') + continue; + + $components[] = $component; + + } + + return $components; + + } + + /** + * Returns the first component that is not a VTIMEZONE, and does not have + * an RECURRENCE-ID. + * + * If there is no such component, null will be returned. + * + * @param string $componentName filter by component name + * @return VObject\Component|null + */ + function getBaseComponent($componentName = null) { + + foreach($this->children as $component) { + + if (!$component instanceof VObject\Component) + continue; + + if (isset($component->{'RECURRENCE-ID'})) + continue; + + if ($componentName && $component->name !== strtoupper($componentName)) + continue; + + if ($component->name === 'VTIMEZONE') + continue; + + return $component; + + } + + } + + /** + * If this calendar object, has events with recurrence rules, this method + * can be used to expand the event into multiple sub-events. + * + * Each event will be stripped from it's recurrence information, and only + * the instances of the event in the specified timerange will be left + * alone. + * + * In addition, this method will cause timezone information to be stripped, + * and normalized to UTC. + * + * This method will alter the VCalendar. This cannot be reversed. + * + * This functionality is specifically used by the CalDAV standard. It is + * possible for clients to request expand events, if they are rather simple + * clients and do not have the possibility to calculate recurrences. + * + * @param DateTime $start + * @param DateTime $end + * @param DateTimeZone $timeZone reference timezone for floating dates and + * times. + * @return void + */ + function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) { + + $newEvents = array(); + + if (!$timeZone) { + $timeZone = new DateTimeZone('UTC'); + } + + // An array of events. Events are indexed by UID. Each item in this + // array is a list of one or more events that match the UID. + $recurringEvents = array(); + + foreach($this->select('VEVENT') as $key=>$vevent) { + + $uid = (string)$vevent->UID; + if (!$uid) { + throw new \LogicException('Event did not have a UID!'); + } + + if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) { + if (isset($recurringEvents[$uid])) { + $recurringEvents[$uid][] = $vevent; + } else { + $recurringEvents[$uid] = array($vevent); + } + continue; + } + + if (!isset($vevent->RRULE)) { + if ($vevent->isInTimeRange($start, $end)) { + $newEvents[] = $vevent; + } + continue; + } + + } + + foreach($recurringEvents as $events) { + + try { + $it = new EventIterator($events, $timeZone); + + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + continue; + } + $it->fastForward($start); + + while($it->valid() && $it->getDTStart() < $end) { + + if ($it->getDTEnd() > $start) { + + $newEvents[] = $it->getEventObject(); + + } + $it->next(); + + } + + } + + // Wiping out all old VEVENT objects + unset($this->VEVENT); + + // Setting all properties to UTC time. + foreach($newEvents as $newEvent) { + + foreach($newEvent->children as $child) { + if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) { + $dt = $child->getDateTimes($timeZone); + // We only need to update the first timezone, because + // setDateTimes will match all other timezones to the + // first. + $dt[0]->setTimeZone(new DateTimeZone('UTC')); + $child->setDateTimes($dt); + } + + } + $this->add($newEvent); + + } + + // Removing all VTIMEZONE components + unset($this->VTIMEZONE); + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return array( + 'VERSION' => '2.0', + 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', + 'CALSCALE' => 'GREGORIAN', + ); + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + function getValidationRules() { + + return array( + 'PRODID' => 1, + 'VERSION' => 1, + + 'CALSCALE' => '?', + 'METHOD' => '?', + ); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. + * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on). + * 2 - A warning. + * 3 - An error. + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $warnings = parent::validate($options); + + if ($ver = $this->VERSION) { + if ((string)$ver !== '2.0') { + $warnings[] = array( + 'level' => 3, + 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', + 'node' => $this, + ); + } + + } + + $uidList = array(); + + $componentsFound = 0; + + $componentTypes = array(); + + foreach($this->children as $child) { + if($child instanceof Component) { + $componentsFound++; + + if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) { + continue; + } + $componentTypes[] = $child->name; + + $uid = (string)$child->UID; + $isMaster = isset($child->{'RECURRENCE-ID'})?0:1; + if (isset($uidList[$uid])) { + $uidList[$uid]['count']++; + if ($isMaster && $uidList[$uid]['hasMaster']) { + $warnings[] = array( + 'level' => 3, + 'message' => 'More than one master object was found for the object with UID ' . $uid, + 'node' => $this, + ); + } + $uidList[$uid]['hasMaster']+=$isMaster; + } else { + $uidList[$uid] = array( + 'count' => 1, + 'hasMaster' => $isMaster, + ); + } + + } + } + + if ($componentsFound===0) { + $warnings[] = array( + 'level' => 3, + 'message' => 'An iCalendar object must have at least 1 component.', + 'node' => $this, + ); + } + + if ($options & self::PROFILE_CALDAV) { + if (count($uidList)>1) { + $warnings[] = array( + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', + 'node' => $this, + ); + } + if (count(array_unique($componentTypes))===0) { + $warnings[] = array( + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', + 'node' => $this, + ); + } + if (count(array_unique($componentTypes))>1) { + $warnings[] = array( + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', + 'node' => $this, + ); + } + + if (isset($this->METHOD)) { + $warnings[] = array( + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', + 'node' => $this, + ); + } + } + + return $warnings; + + } + + /** + * Returns all components with a specific UID value. + * + * @return array + */ + function getByUID($uid) { + + return array_filter($this->children, function($item) use ($uid) { + + if (!$item instanceof Component) { + return false; + } + if (!$itemUid = $item->select('UID')) { + return false; + } + $itemUid = current($itemUid)->getValue(); + return $uid === $itemUid; + + }); + + } + + +} diff --git a/vendor/sabre/vobject/lib/Component/VCard.php b/vendor/sabre/vobject/lib/Component/VCard.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VCard.php @@ -0,0 +1,452 @@ + 'Sabre\\VObject\\Property\\Binary', + 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', + 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only + 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date', + 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime', + 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only + 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', + 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', + 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', + 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', + 'TEXT' => 'Sabre\\VObject\\Property\\Text', + 'TIME' => 'Sabre\\VObject\\Property\\Time', + 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. + 'URI' => 'Sabre\\VObject\\Property\\Uri', + 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only + 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + ); + + /** + * List of properties, and which classes they map to. + * + * @var array + */ + static $propertyMap = array( + + // vCard 2.1 properties and up + 'N' => 'Sabre\\VObject\\Property\\Text', + 'FN' => 'Sabre\\VObject\\Property\\FlatText', + 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values. + 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + 'ADR' => 'Sabre\\VObject\\Property\\Text', + 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + 'TEL' => 'Sabre\\VObject\\Property\\FlatText', + 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText', + 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + 'GEO' => 'Sabre\\VObject\\Property\\FlatText', + 'TITLE' => 'Sabre\\VObject\\Property\\FlatText', + 'ROLE' => 'Sabre\\VObject\\Property\\FlatText', + 'LOGO' => 'Sabre\\VObject\\Property\\Binary', + // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so + // not supported at the moment + 'ORG' => 'Sabre\\VObject\\Property\\Text', + 'NOTE' => 'Sabre\\VObject\\Property\\FlatText', + 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', + 'SOUND' => 'Sabre\\VObject\\Property\\FlatText', + 'URL' => 'Sabre\\VObject\\Property\\Uri', + 'UID' => 'Sabre\\VObject\\Property\\FlatText', + 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', + 'KEY' => 'Sabre\\VObject\\Property\\FlatText', + 'TZ' => 'Sabre\\VObject\\Property\\Text', + + // vCard 3.0 properties + 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', + 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText', + 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', + 'NICKNAME' => 'Sabre\\VObject\\Property\\Text', + 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + + // rfc2739 properties + 'FBURL' => 'Sabre\\VObject\\Property\\Uri', + 'CAPURI' => 'Sabre\\VObject\\Property\\Uri', + 'CALURI' => 'Sabre\\VObject\\Property\\Uri', + + // rfc4770 properties + 'IMPP' => 'Sabre\\VObject\\Property\\Uri', + + // vCard 4.0 properties + 'XML' => 'Sabre\\VObject\\Property\\FlatText', + 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text', + 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', + 'GENDER' => 'Sabre\\VObject\\Property\\Text', + 'KIND' => 'Sabre\\VObject\\Property\\FlatText', + + // rfc6474 properties + 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText', + 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText', + 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + + // rfc6715 properties + 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText', + 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText', + 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText', + 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText', + + ); + + /** + * Returns the current document type. + * + * @return void + */ + function getDocumentType() { + + if (!$this->version) { + $version = (string)$this->VERSION; + switch($version) { + case '2.1' : + $this->version = self::VCARD21; + break; + case '3.0' : + $this->version = self::VCARD30; + break; + case '4.0' : + $this->version = self::VCARD40; + break; + default : + $this->version = self::UNKNOWN; + break; + + } + } + + return $this->version; + + } + + /** + * Converts the document to a different vcard version. + * + * Use one of the VCARD constants for the target. This method will return + * a copy of the vcard in the new version. + * + * At the moment the only supported conversion is from 3.0 to 4.0. + * + * If input and output version are identical, a clone is returned. + * + * @param int $target + * @return VCard + */ + function convert($target) { + + $converter = new VObject\VCardConverter(); + return $converter->convert($this, $target); + + } + + /** + * VCards with version 2.1, 3.0 and 4.0 are found. + * + * If the VCARD doesn't know its version, 2.1 is assumed. + */ + const DEFAULT_VERSION = self::VCARD21; + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on) + * 2 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $warnings = array(); + + $versionMap = array( + self::VCARD21 => '2.1', + self::VCARD30 => '3.0', + self::VCARD40 => '4.0', + ); + + $version = $this->select('VERSION'); + if (count($version)===1) { + $version = (string)$this->VERSION; + if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') { + $warnings[] = array( + 'level' => 3, + 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + 'node' => $this, + ); + if ($options & self::REPAIR) { + $this->VERSION = $versionMap[self::DEFAULT_VERSION]; + } + } + if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) { + $warnings[] = array( + 'level' => 3, + 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.', + 'node' => $this, + ); + } + + } + $uid = $this->select('UID'); + if (count($uid) === 0) { + if ($options & self::PROFILE_CARDDAV) { + // Required for CardDAV + $warningLevel = 3; + $message = 'vCards on CardDAV servers MUST have a UID property.'; + } else { + // Not required for regular vcards + $warningLevel = 2; + $message = 'Adding a UID to a vCard property is recommended.'; + } + if ($options & self::REPAIR) { + $this->UID = VObject\UUIDUtil::getUUID(); + $warningLevel = 1; + } + $warnings[] = array( + 'level' => $warningLevel, + 'message' => $message, + 'node' => $this, + ); + } + + $fn = $this->select('FN'); + if (count($fn)!==1) { + + $repaired = false; + if (($options & self::REPAIR) && count($fn) === 0) { + // We're going to try to see if we can use the contents of the + // N property. + if (isset($this->N)) { + $value = explode(';', (string)$this->N); + if (isset($value[1]) && $value[1]) { + $this->FN = $value[1] . ' ' . $value[0]; + } else { + $this->FN = $value[0]; + } + $repaired = true; + + // Otherwise, the ORG property may work + } elseif (isset($this->ORG)) { + $this->FN = (string)$this->ORG; + $repaired = true; + } + + } + $warnings[] = array( + 'level' => $repaired?1:3, + 'message' => 'The FN property must appear in the VCARD component exactly 1 time', + 'node' => $this, + ); + } + + return array_merge( + parent::validate($options), + $warnings + ); + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + function getValidationRules() { + + return array( + 'ADR' => '*', + 'ANNIVERSARY' => '?', + 'BDAY' => '?', + 'CALADRURI' => '*', + 'CALURI' => '*', + 'CATEGORIES' => '*', + 'CLIENTPIDMAP' => '*', + 'EMAIL' => '*', + 'FBURL' => '*', + 'IMPP' => '*', + 'GENDER' => '?', + 'GEO' => '*', + 'KEY' => '*', + 'KIND' => '?', + 'LANG' => '*', + 'LOGO' => '*', + 'MEMBER' => '*', + 'N' => '?', + 'NICKNAME' => '*', + 'NOTE' => '*', + 'ORG' => '*', + 'PHOTO' => '*', + 'PRODID' => '?', + 'RELATED' => '*', + 'REV' => '?', + 'ROLE' => '*', + 'SOUND' => '*', + 'SOURCE' => '*', + 'TEL' => '*', + 'TITLE' => '*', + 'TZ' => '*', + 'URL' => '*', + 'VERSION' => '1', + 'XML' => '*', + + // FN is commented out, because it's already handled by the + // validate function, which may also try to repair it. + // 'FN' => '+', + + 'UID' => '?', + ); + + } + + /** + * Returns a preferred field. + * + * VCards can indicate wether a field such as ADR, TEL or EMAIL is + * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x + * being a number between 1 and 100). + * + * If neither of those parameters are specified, the first is returned, if + * a field with that name does not exist, null is returned. + * + * @param string $fieldName + * @return VObject\Property|null + */ + function preferred($propertyName) { + + $preferred = null; + $lastPref = 101; + foreach($this->select($propertyName) as $field) { + + $pref = 101; + if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) { + $pref = 1; + } elseif (isset($field['PREF'])) { + $pref = $field['PREF']->getValue(); + } + + if ($pref < $lastPref || is_null($preferred)) { + $preferred = $field; + $lastPref = $pref; + } + + } + return $preferred; + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return array( + 'VERSION' => '3.0', + 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', + ); + + } + + /** + * This method returns an array, with the representation as it should be + * encoded in json. This is used to create jCard or jCal documents. + * + * @return array + */ + function jsonSerialize() { + + // A vcard does not have sub-components, so we're overriding this + // method to remove that array element. + $properties = array(); + + foreach($this->children as $child) { + $properties[] = $child->jsonSerialize(); + } + + return array( + strtolower($this->name), + $properties, + ); + + } + + /** + * Returns the default class for a property name. + * + * @param string $propertyName + * @return string + */ + function getClassNameForPropertyName($propertyName) { + + $className = parent::getClassNameForPropertyName($propertyName); + // In vCard 4, BINARY no longer exists, and we need URI instead. + + if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) { + return 'Sabre\\VObject\\Property\\Uri'; + } + return $className; + + } + +} + diff --git a/vendor/sabre/vobject/lib/Component/VEvent.php b/vendor/sabre/vobject/lib/Component/VEvent.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VEvent.php @@ -0,0 +1,153 @@ +RRULE) { + + try { + + $it = new EventIterator($this, null, $start->getTimezone()); + + } catch (NoInstancesException $e) { + + // If we've catched this exception, there are no instances + // for the event that fall into the specified time-range. + return false; + + } + + $it->fastForward($start); + + // We fast-forwarded to a spot where the end-time of the + // recurrence instance exceeded the start of the requested + // time-range. + // + // If the starttime of the recurrence did not exceed the + // end of the time range as well, we have a match. + return ($it->getDTStart() < $end && $it->getDTEnd() > $start); + + } + + $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); + if (isset($this->DTEND)) { + + // The DTEND property is considered non inclusive. So for a 3 day + // event in july, dtstart and dtend would have to be July 1st and + // July 4th respectively. + // + // See: + // http://tools.ietf.org/html/rfc5545#page-54 + $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); + + } elseif (isset($this->DURATION)) { + $effectiveEnd = clone $effectiveStart; + $effectiveEnd->add(VObject\DateTimeParser::parseDuration($this->DURATION)); + } elseif (!$this->DTSTART->hasTime()) { + $effectiveEnd = clone $effectiveStart; + $effectiveEnd->modify('+1 day'); + } else { + $effectiveEnd = clone $effectiveStart; + } + return ( + ($start < $effectiveEnd) && ($end > $effectiveStart) + ); + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return array( + 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), + 'DTSTAMP' => date('Ymd\\THis\\Z'), + ); + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + public function getValidationRules() { + + $hasMethod = isset($this->parent->METHOD); + return array( + 'UID' => 1, + 'DTSTAMP' => 1, + 'DTSTART' => $hasMethod?'?':'1', + 'CLASS' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'GEO' => '?', + 'LAST-MODIFIED' => '?', + 'LOCATION' => '?', + 'ORGANIZER' => '?', + 'PRIORITY' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'TRANSP' => '?', + 'URL' => '?', + 'RECURRENCE-ID' => '?', + 'RRULE' => '?', + 'DTEND' => '?', + 'DURATION' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'REQUEST-STATUS' => '*', + 'RELATED-TO' => '*', + 'RESOURCES' => '*', + 'RDATE' => '*', + ); + + } + +} diff --git a/vendor/sabre/vobject/lib/Component/VFreeBusy.php b/vendor/sabre/vobject/lib/Component/VFreeBusy.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VFreeBusy.php @@ -0,0 +1,103 @@ +select('FREEBUSY') as $freebusy) { + + // We are only interested in FBTYPE=BUSY (the default), + // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. + if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') { + continue; + } + + // The freebusy component can hold more than 1 value, separated by + // commas. + $periods = explode(',', (string)$freebusy); + + foreach($periods as $period) { + // Every period is formatted as [start]/[end]. The start is an + // absolute UTC time, the end may be an absolute UTC time, or + // duration (relative) value. + list($busyStart, $busyEnd) = explode('/', $period); + + $busyStart = VObject\DateTimeParser::parse($busyStart); + $busyEnd = VObject\DateTimeParser::parse($busyEnd); + if ($busyEnd instanceof \DateInterval) { + $tmp = clone $busyStart; + $tmp->add($busyEnd); + $busyEnd = $tmp; + } + + if($start < $busyEnd && $end > $busyStart) { + return false; + } + + } + + } + + return true; + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + public function getValidationRules() { + + return array( + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CONTACT' => '?', + 'DTSTART' => '?', + 'DTEND' => '?', + 'ORGANIZER' => '?', + 'URL' => '?', + + 'ATTENDEE' => '*', + 'COMMENT' => '*', + 'FREEBUSY' => '*', + 'REQUEST-STATUS' => '*', + ); + + } + +} + diff --git a/vendor/sabre/vobject/lib/Component/VJournal.php b/vendor/sabre/vobject/lib/Component/VJournal.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VJournal.php @@ -0,0 +1,91 @@ +DTSTART)?$this->DTSTART->getDateTime():null; + if ($dtstart) { + $effectiveEnd = clone $dtstart; + if (!$this->DTSTART->hasTime()) { + $effectiveEnd->modify('+1 day'); + } + + return ($start <= $effectiveEnd && $end > $dtstart); + + } + return false; + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + public function getValidationRules() { + + return array( + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CLASS' => '?', + 'CREATED' => '?', + 'DTSTART' => '?', + 'LAST-MODIFIED' => '?', + 'ORGANIZER' => '?', + 'RECURRENCE-ID' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + + 'RRULE' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'DESCRIPTION' => '*', + 'EXDATE' => '*', + 'RELATED-TO' => '*', + 'RDATE' => '*', + ); + + } +} diff --git a/vendor/sabre/vobject/lib/Component/VTimeZone.php b/vendor/sabre/vobject/lib/Component/VTimeZone.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VTimeZone.php @@ -0,0 +1,68 @@ +TZID, $this->root); + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + function getValidationRules() { + + return array( + 'TZID' => 1, + + 'LAST-MODIFIED' => '?', + 'TZURL' => '?', + + // At least 1 STANDARD or DAYLIGHT must appear, or more. But both + // cannot appear in the same VTIMEZONE. + // + // The validator is not specific yet to pick this up, so these + // rules are too loose. + 'STANDARD' => '*', + 'DAYLIGHT' => '*', + ); + + } + +} + diff --git a/vendor/sabre/vobject/lib/Component/VTodo.php b/vendor/sabre/vobject/lib/Component/VTodo.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Component/VTodo.php @@ -0,0 +1,177 @@ +DTSTART)?$this->DTSTART->getDateTime():null; + $duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null; + $due = isset($this->DUE)?$this->DUE->getDateTime():null; + $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null; + $created = isset($this->CREATED)?$this->CREATED->getDateTime():null; + + if ($dtstart) { + if ($duration) { + $effectiveEnd = clone $dtstart; + $effectiveEnd->add($duration); + return $start <= $effectiveEnd && $end > $dtstart; + } elseif ($due) { + return + ($start < $due || $start <= $dtstart) && + ($end > $dtstart || $end >= $due); + } else { + return $start <= $dtstart && $end > $dtstart; + } + } + if ($due) { + return ($start < $due && $end >= $due); + } + if ($completed && $created) { + return + ($start <= $created || $start <= $completed) && + ($end >= $created || $end >= $completed); + } + if ($completed) { + return ($start <= $completed && $end >= $completed); + } + if ($created) { + return ($end > $created); + } + return true; + + } + + /** + * A simple list of validation rules. + * + * This is simply a list of properties, and how many times they either + * must or must not appear. + * + * Possible values per property: + * * 0 - Must not appear. + * * 1 - Must appear exactly once. + * * + - Must appear at least once. + * * * - Can appear any number of times. + * * ? - May appear, but not more than once. + * + * @var array + */ + public function getValidationRules() { + + return array( + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CLASS' => '?', + 'COMPLETED' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'DTSTART' => '?', + 'GEO' => '?', + 'LAST-MODIFIED' => '?', + 'LOCATION' => '?', + 'ORGANIZER' => '?', + 'PERCENT' => '?', + 'PRIORITY' => '?', + 'RECURRENCE-ID' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + + 'RRULE' => '?', + 'DUE' => '?', + 'DURATION' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'REQUEST-STATUS' => '*', + 'RELATED-TO' => '*', + 'RESOURCES' => '*', + 'RDATE' => '*', + ); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on) + * 2 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + $result = parent::validate($options); + if (isset($this->DUE) && isset($this->DTSTART)) { + + $due = $this->DUE; + $dtStart = $this->DTSTART; + + if ($due->getValueType() !== $dtStart->getValueType()) { + + $result[] = array( + 'level' => 3, + 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', + 'node' => $due, + ); + + } elseif ($due->getDateTime() < $dtStart->getDateTime()) { + + $result[] = array( + 'level' => 3, + 'message' => 'DUE must occur after DTSTART', + 'node' => $due, + ); + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/vobject/lib/DateTimeParser.php b/vendor/sabre/vobject/lib/DateTimeParser.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/DateTimeParser.php @@ -0,0 +1,431 @@ +setTimeZone(new \DateTimeZone('UTC')); + return $date; + + } + + /** + * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object. + * + * @param string $date + * @param DateTimeZone $tz + * @return DateTime + */ + static public function parseDate($date, DateTimeZone $tz = null) { + + // Format is YYYYMMDD + $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches); + + if (!$result) { + throw new LogicException('The supplied iCalendar date value is incorrect: ' . $date); + } + + if (is_null($tz)) { + $tz = new DateTimeZone('UTC'); + } + + $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz); + return $date; + + } + + /** + * Parses an iCalendar (RFC5545) formatted duration value. + * + * This method will either return a DateTimeInterval object, or a string + * suitable for strtotime or DateTime::modify. + * + * @param string $duration + * @param bool $asString + * @return DateInterval|string + */ + static public function parseDuration($duration, $asString = false) { + + $result = preg_match('/^(?P\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$/', $duration, $matches); + if (!$result) { + throw new LogicException('The supplied iCalendar duration value is incorrect: ' . $duration); + } + + if (!$asString) { + $invert = false; + if ($matches['plusminus']==='-') { + $invert = true; + } + + + $parts = array( + 'week', + 'day', + 'hour', + 'minute', + 'second', + ); + foreach($parts as $part) { + $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0; + } + + + // We need to re-construct the $duration string, because weeks and + // days are not supported by DateInterval in the same string. + $duration = 'P'; + $days = $matches['day']; + if ($matches['week']) { + $days+=$matches['week']*7; + } + if ($days) + $duration.=$days . 'D'; + + if ($matches['minute'] || $matches['second'] || $matches['hour']) { + $duration.='T'; + + if ($matches['hour']) + $duration.=$matches['hour'].'H'; + + if ($matches['minute']) + $duration.=$matches['minute'].'M'; + + if ($matches['second']) + $duration.=$matches['second'].'S'; + + } + + if ($duration==='P') { + $duration = 'PT0S'; + } + $iv = new DateInterval($duration); + if ($invert) $iv->invert = true; + + return $iv; + + } + + + + $parts = array( + 'week', + 'day', + 'hour', + 'minute', + 'second', + ); + + $newDur = ''; + foreach($parts as $part) { + if (isset($matches[$part]) && $matches[$part]) { + $newDur.=' '.$matches[$part] . ' ' . $part . 's'; + } + } + + $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur); + if ($newDur === '+') { + $newDur = '+0 seconds'; + }; + return $newDur; + + } + + /** + * Parses either a Date or DateTime, or Duration value. + * + * @param string $date + * @param DateTimeZone|string $referenceTz + * @return DateTime|DateInterval + */ + static public function parse($date, $referenceTz = null) { + + if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) { + return self::parseDuration($date); + } elseif (strlen($date)===8) { + return self::parseDate($date, $referenceTz); + } else { + return self::parseDateTime($date, $referenceTz); + } + + } + + /** + * This method parses a vCard date and or time value. + * + * This can be used for the DATE, DATE-TIME, TIMESTAMP and + * DATE-AND-OR-TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * year, month, date, hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the year, etc. + * + * Timezone is either returned as 'Z' or as '+08:00' + * + * For any non-specified values null is returned. + * + * List of date formats that are supported: + * YYYY + * YYYY-MM + * YYYYMMDD + * --MMDD + * ---DD + * + * YYYY-MM-DD + * --MM-DD + * ---DD + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format date-time string looks like : + * 20130603T133901 + * + * A full extended-format date-time string looks like : + * 2013-06-03T13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +1100. + * + * @param string $date + * @return array + */ + static public function parseVCardDateTime($date) { + + $regex = '/^ + (?: # date part + (?: + (?: (?P [0-9]{4}) (?: -)?| --) + (?P [0-9]{2})? + |---) + (?P [0-9]{2})? + )? + (?:T # time part + (?P [0-9]{2} | -) + (?P [0-9]{2} | -)? + (?P [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: # date part + (?: (?P [0-9]{4}) - | -- ) + (?P [0-9]{2}) - + (?P [0-9]{2}) + )? + (?:T # time part + + (?: (?P [0-9]{2}) : | -) + (?: (?P [0-9]{2}) : | -)? + (?P [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidArgumentException('Invalid vCard date-time string: ' . $date); + } + + } + $parts = array( + 'year', + 'month', + 'date', + 'hour', + 'minute', + 'second', + 'timezone' + ); + + $result = array(); + foreach($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-' || $matches[$part] === '--') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } + + /** + * This method parses a vCard TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the hour etc. + * + * Timezone is either returned as 'Z' or as '+08:00' + * + * For any non-specified values null is returned. + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format time string looks like : + * 133901 + * + * A full extended-format time string looks like : + * 13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +11:00. + * + * @param string $date + * @return array + */ + static public function parseVCardTime($date) { + + $regex = '/^ + (?P [0-9]{2} | -) + (?P [0-9]{2} | -)? + (?P [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + $/x'; + + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: (?P [0-9]{2}) : | -) + (?: (?P [0-9]{2}) : | -)? + (?P [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidArgumentException('Invalid vCard time string: ' . $date); + } + + } + $parts = array( + 'hour', + 'minute', + 'second', + 'timezone' + ); + + $result = array(); + foreach($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } +} diff --git a/vendor/sabre/vobject/lib/Document.php b/vendor/sabre/vobject/lib/Document.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Document.php @@ -0,0 +1,261 @@ +value syntax, in which case + * properties will automatically be created, or you can just pass a list of + * Component and Property object. + * + * By default, a set of sensible values will be added to the component. For + * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To + * ensure that this does not happen, set $defaults to false. + * + * @param string $name + * @param array $children + * @param bool $defaults + * @return Component + */ + public function createComponent($name, array $children = null, $defaults = true) { + + $name = strtoupper($name); + $class = 'Sabre\\VObject\\Component'; + + if (isset(static::$componentMap[$name])) { + $class=static::$componentMap[$name]; + } + if (is_null($children)) $children = array(); + return new $class($this, $name, $children, $defaults); + + } + + /** + * Factory method for creating new properties + * + * This method automatically searches for the correct property class, based + * on its name. + * + * You can specify the parameters either in key=>value syntax, in which case + * parameters will automatically be created, or you can just pass a list of + * Parameter objects. + * + * @param string $name + * @param mixed $value + * @param array $parameters + * @param string $valueType Force a specific valuetype, such as URI or TEXT + * @return Property + */ + public function createProperty($name, $value = null, array $parameters = null, $valueType = null) { + + // If there's a . in the name, it means it's prefixed by a groupname. + if (($i=strpos($name,'.'))!==false) { + $group = substr($name, 0, $i); + $name = strtoupper(substr($name, $i+1)); + } else { + $name = strtoupper($name); + $group = null; + } + + $class = null; + + if ($valueType) { + // The valueType argument comes first to figure out the correct + // class. + $class = $this->getClassNameForPropertyValue($valueType); + } + + if (is_null($class) && isset($parameters['VALUE'])) { + // If a VALUE parameter is supplied, we should use that. + $class = $this->getClassNameForPropertyValue($parameters['VALUE']); + } + if (is_null($class)) { + $class = $this->getClassNameForPropertyName($name); + } + if (is_null($parameters)) $parameters = array(); + + return new $class($this, $name, $value, $parameters, $group); + + } + + /** + * This method returns a full class-name for a value parameter. + * + * For instance, DTSTART may have VALUE=DATE. In that case we will look in + * our valueMap table and return the appropriate class name. + * + * This method returns null if we don't have a specialized class. + * + * @param string $valueParam + * @return void + */ + public function getClassNameForPropertyValue($valueParam) { + + $valueParam = strtoupper($valueParam); + if (isset(static::$valueMap[$valueParam])) { + return static::$valueMap[$valueParam]; + } + + } + + /** + * Returns the default class for a property name. + * + * @param string $propertyName + * @return string + */ + public function getClassNameForPropertyName($propertyName) { + + if (isset(static::$propertyMap[$propertyName])) { + return static::$propertyMap[$propertyName]; + } else { + return 'Sabre\\VObject\\Property\\Unknown'; + } + + } + +} diff --git a/vendor/sabre/vobject/lib/ElementList.php b/vendor/sabre/vobject/lib/ElementList.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/ElementList.php @@ -0,0 +1,172 @@ +vevent where there's multiple VEVENT objects. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ElementList implements \Iterator, \Countable, \ArrayAccess { + + /** + * Inner elements + * + * @var array + */ + protected $elements = array(); + + /** + * Creates the element list. + * + * @param array $elements + */ + public function __construct(array $elements) { + + $this->elements = $elements; + + } + + /* {{{ Iterator interface */ + + /** + * Current position + * + * @var int + */ + private $key = 0; + + /** + * Returns current item in iteration + * + * @return Element + */ + public function current() { + + return $this->elements[$this->key]; + + } + + /** + * To the next item in the iterator + * + * @return void + */ + public function next() { + + $this->key++; + + } + + /** + * Returns the current iterator key + * + * @return int + */ + public function key() { + + return $this->key; + + } + + /** + * Returns true if the current position in the iterator is a valid one + * + * @return bool + */ + public function valid() { + + return isset($this->elements[$this->key]); + + } + + /** + * Rewinds the iterator + * + * @return void + */ + public function rewind() { + + $this->key = 0; + + } + + /* }}} */ + + /* {{{ Countable interface */ + + /** + * Returns the number of elements + * + * @return int + */ + public function count() { + + return count($this->elements); + + } + + /* }}} */ + + /* {{{ ArrayAccess Interface */ + + + /** + * Checks if an item exists through ArrayAccess. + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) { + + return isset($this->elements[$offset]); + + } + + /** + * Gets an item through ArrayAccess. + * + * @param int $offset + * @return mixed + */ + public function offsetGet($offset) { + + return $this->elements[$offset]; + + } + + /** + * Sets an item through ArrayAccess. + * + * @param int $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) { + + throw new \LogicException('You can not add new objects to an ElementList'); + + } + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @return void + */ + public function offsetUnset($offset) { + + throw new \LogicException('You can not remove objects from an ElementList'); + + } + + /* }}} */ + +} diff --git a/vendor/sabre/vobject/lib/EofException.php b/vendor/sabre/vobject/lib/EofException.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/EofException.php @@ -0,0 +1,15 @@ +setTimeRange($start, $end); + } + + if ($objects) { + $this->setObjects($objects); + } + if (is_null($timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->setTimeZone($timeZone); + + } + + /** + * Sets the VCALENDAR object. + * + * If this is set, it will not be generated for you. You are responsible + * for setting things like the METHOD, CALSCALE, VERSION, etc.. + * + * The VFREEBUSY object will be automatically added though. + * + * @param Component $vcalendar + * @return void + */ + public function setBaseObject(Component $vcalendar) { + + $this->baseObject = $vcalendar; + + } + + /** + * Sets the input objects + * + * You must either specify a valendar object as a strong, or as the parse + * Component. + * It's also possible to specify multiple objects as an array. + * + * @param mixed $objects + * @return void + */ + public function setObjects($objects) { + + if (!is_array($objects)) { + $objects = array($objects); + } + + $this->objects = array(); + foreach($objects as $object) { + + if (is_string($object)) { + $this->objects[] = Reader::read($object); + } elseif ($object instanceof Component) { + $this->objects[] = $object; + } else { + throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); + } + + } + + } + + /** + * Sets the time range + * + * Any freebusy object falling outside of this time range will be ignored. + * + * @param DateTime $start + * @param DateTime $end + * @return void + */ + public function setTimeRange(\DateTime $start = null, \DateTime $end = null) { + + $this->start = $start; + $this->end = $end; + + } + + /** + * Sets the reference timezone for floating times. + * + * @param DateTimeZone $timeZone + * @return void + */ + public function setTimeZone(DateTimeZone $timeZone) { + + $this->timeZone = $timeZone; + + } + + /** + * Parses the input data and returns a correct VFREEBUSY object, wrapped in + * a VCALENDAR. + * + * @return Component + */ + public function getResult() { + + $busyTimes = array(); + + foreach($this->objects as $key=>$object) { + + foreach($object->getBaseComponents() as $component) { + + switch($component->name) { + + case 'VEVENT' : + + $FBTYPE = 'BUSY'; + if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { + break; + } + if (isset($component->STATUS)) { + $status = strtoupper($component->STATUS); + if ($status==='CANCELLED') { + break; + } + if ($status==='TENTATIVE') { + $FBTYPE = 'BUSY-TENTATIVE'; + } + } + + $times = array(); + + if ($component->RRULE) { + try { + $iterator = new EventIterator($object, (string)$component->uid, $this->timeZone); + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + unset($this->objects[$key]); + continue; + } + + if ($this->start) { + $iterator->fastForward($this->start); + } + + $maxRecurrences = 200; + + while($iterator->valid() && --$maxRecurrences) { + + $startTime = $iterator->getDTStart(); + if ($this->end && $startTime > $this->end) { + break; + } + $times[] = array( + $iterator->getDTStart(), + $iterator->getDTEnd(), + ); + + $iterator->next(); + + } + + } else { + + $startTime = $component->DTSTART->getDateTime($this->timeZone); + if ($this->end && $startTime > $this->end) { + break; + } + $endTime = null; + if (isset($component->DTEND)) { + $endTime = $component->DTEND->getDateTime($this->timeZone); + } elseif (isset($component->DURATION)) { + $duration = DateTimeParser::parseDuration((string)$component->DURATION); + $endTime = clone $startTime; + $endTime->add($duration); + } elseif (!$component->DTSTART->hasTime()) { + $endTime = clone $startTime; + $endTime->modify('+1 day'); + } else { + // The event had no duration (0 seconds) + break; + } + + $times[] = array($startTime, $endTime); + + } + + foreach($times as $time) { + + if ($this->end && $time[0] > $this->end) break; + if ($this->start && $time[1] < $this->start) break; + + $busyTimes[] = array( + $time[0], + $time[1], + $FBTYPE, + ); + } + break; + + case 'VFREEBUSY' : + foreach($component->FREEBUSY as $freebusy) { + + $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY'; + + // Skipping intervals marked as 'free' + if ($fbType==='FREE') + continue; + + $values = explode(',', $freebusy); + foreach($values as $value) { + list($startTime, $endTime) = explode('/', $value); + $startTime = DateTimeParser::parseDateTime($startTime); + + if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') { + $duration = DateTimeParser::parseDuration($endTime); + $endTime = clone $startTime; + $endTime->add($duration); + } else { + $endTime = DateTimeParser::parseDateTime($endTime); + } + + if($this->start && $this->start > $endTime) continue; + if($this->end && $this->end < $startTime) continue; + $busyTimes[] = array( + $startTime, + $endTime, + $fbType + ); + + } + + + } + break; + + + + } + + + } + + } + + if ($this->baseObject) { + $calendar = $this->baseObject; + } else { + $calendar = new VCalendar(); + } + + $vfreebusy = $calendar->createComponent('VFREEBUSY'); + $calendar->add($vfreebusy); + + if ($this->start) { + $dtstart = $calendar->createProperty('DTSTART'); + $dtstart->setDateTime($this->start); + $vfreebusy->add($dtstart); + } + if ($this->end) { + $dtend = $calendar->createProperty('DTEND'); + $dtend->setDateTime($this->end); + $vfreebusy->add($dtend); + } + $dtstamp = $calendar->createProperty('DTSTAMP'); + $dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC'))); + $vfreebusy->add($dtstamp); + + foreach($busyTimes as $busyTime) { + + $busyTime[0]->setTimeZone(new \DateTimeZone('UTC')); + $busyTime[1]->setTimeZone(new \DateTimeZone('UTC')); + + $prop = $calendar->createProperty( + 'FREEBUSY', + $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') + ); + $prop['FBTYPE'] = $busyTime[2]; + $vfreebusy->add($prop); + + } + + return $calendar; + + } + +} diff --git a/vendor/sabre/vobject/lib/ITip/Broker.php b/vendor/sabre/vobject/lib/ITip/Broker.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/ITip/Broker.php @@ -0,0 +1,981 @@ +component !== 'VEVENT') { + return false; + } + + switch($itipMessage->method) { + + case 'REQUEST' : + return $this->processMessageRequest($itipMessage, $existingObject); + + case 'CANCEL' : + return $this->processMessageCancel($itipMessage, $existingObject); + + case 'REPLY' : + return $this->processMessageReply($itipMessage, $existingObject); + + default : + // Unsupported iTip message + return null; + + } + + return $existingObject; + + } + + /** + * This function parses a VCALENDAR object and figure out if any messages + * need to be sent. + * + * A VCALENDAR object will be created from the perspective of either an + * attendee, or an organizer. You must pass a string identifying the + * current user, so we can figure out who in the list of attendees or the + * organizer we are sending this message on behalf of. + * + * It's possible to specify the current user as an array, in case the user + * has more than one identifying href (such as multiple emails). + * + * It $oldCalendar is specified, it is assumed that the operation is + * updating an existing event, which means that we need to look at the + * differences between events, and potentially send old attendees + * cancellations, and current attendees updates. + * + * If $calendar is null, but $oldCalendar is specified, we treat the + * operation as if the user has deleted an event. If the user was an + * organizer, this means that we need to send cancellation notices to + * people. If the user was an attendee, we need to make sure that the + * organizer gets the 'declined' message. + * + * @param VCalendar|string $calendar + * @param string|array $userHref + * @param VCalendar|string $oldCalendar + * @return array + */ + public function parseEvent($calendar = null, $userHref, $oldCalendar = null) { + + if ($oldCalendar) { + if (is_string($oldCalendar)) { + $oldCalendar = Reader::read($oldCalendar); + } + if (!isset($oldCalendar->VEVENT)) { + // We only support events at the moment + return array(); + } + + $oldEventInfo = $this->parseEventInfo($oldCalendar); + } else { + $oldEventInfo = array( + 'organizer' => null, + 'significantChangeHash' => '', + 'attendees' => array(), + ); + } + + $userHref = (array)$userHref; + + if (!is_null($calendar)) { + + if (is_string($calendar)) { + $calendar = Reader::read($calendar); + } + if (!isset($calendar->VEVENT)) { + // We only support events at the moment + return array(); + } + $eventInfo = $this->parseEventInfo($calendar); + if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) { + // If there were no attendees on either side of the equation, + // we don't need to do anything. + return array(); + } + if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) { + // There was no organizer before or after the change. + return array(); + } + + $baseCalendar = $calendar; + + // If the new object didn't have an organizer, the organizer + // changed the object from a scheduling object to a non-scheduling + // object. We just copy the info from the old object. + if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) { + $eventInfo['organizer'] = $oldEventInfo['organizer']; + $eventInfo['organizerName'] = $oldEventInfo['organizerName']; + } + + } else { + // The calendar object got deleted, we need to process this as a + // cancellation / decline. + if (!$oldCalendar) { + // No old and no new calendar, there's no thing to do. + return array(); + } + + $eventInfo = $oldEventInfo; + + if (in_array($eventInfo['organizer'], $userHref)) { + // This is an organizer deleting the event. + $eventInfo['attendees'] = array(); + // Increasing the sequence, but only if the organizer deleted + // the event. + $eventInfo['sequence']++; + } else { + // This is an attendee deleting the event. + foreach($eventInfo['attendees'] as $key=>$attendee) { + if (in_array($attendee['href'], $userHref)) { + $eventInfo['attendees'][$key]['instances'] = array('master' => + array('id'=>'master', 'partstat' => 'DECLINED') + ); + } + } + } + $baseCalendar = $oldCalendar; + + } + + if (in_array($eventInfo['organizer'], $userHref)) { + return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); + } elseif ($oldCalendar) { + // We need to figure out if the user is an attendee, but we're only + // doing so if there's an oldCalendar, because we only want to + // process updates, not creation of new events. + foreach($eventInfo['attendees'] as $attendee) { + if (in_array($attendee['href'], $userHref)) { + return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); + } + } + } + return array(); + + } + + /** + * Processes incoming REQUEST messages. + * + * This is message from an organizer, and is either a new event + * invite, or an update to an existing one. + * + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // This is a new invite, and we're just going to copy over + // all the components from the invite. + $existingObject = new VCalendar(); + foreach($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } else { + // We need to update an existing object with all the new + // information. We can just remove all existing components + // and create new ones. + foreach($existingObject->getComponents() as $component) { + $existingObject->remove($component); + } + foreach($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } + return $existingObject; + + } + + /** + * Processes incoming CANCEL messages. + * + * This is a message from an organizer, and means that either an + * attendee got removed from an event, or an event got cancelled + * altogether. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // The event didn't exist in the first place, so we're just + // ignoring this message. + } else { + foreach($existingObject->VEVENT as $vevent) { + $vevent->STATUS = 'CANCELLED'; + $vevent->SEQUENCE = $itipMessage->sequence; + } + } + return $existingObject; + + } + + /** + * Processes incoming REPLY messages. + * + * The message is a reply. This is for example an attendee telling + * an organizer he accepted the invite, or declined it. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) { + + // A reply can only be processed based on an existing object. + // If the object is not available, the reply is ignored. + if (!$existingObject) { + return null; + } + $instances = array(); + $requestStatus = '2.0'; + + // Finding all the instances the attendee replied to. + foreach($itipMessage->message->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; + $attendee = $vevent->ATTENDEE; + $instances[$recurId] = $attendee['PARTSTAT']->getValue(); + if (isset($vevent->{'REQUEST-STATUS'})) { + $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); + list($requestStatus) = explode(';', $requestStatus); + } + } + + // Now we need to loop through the original organizer event, to find + // all the instances where we have a reply for. + $masterObject = null; + foreach($existingObject->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; + if ($recurId==='master') { + $masterObject = $vevent; + } + if (isset($instances[$recurId])) { + $attendeeFound = false; + if (isset($vevent->ATTENDEE)) { + foreach($vevent->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $instances[$recurId]; + $attendee['SCHEDULE-STATUS'] = $requestStatus; + // Un-setting the RSVP status, because we now know + // that the attende already replied. + unset($attendee['RSVP']); + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee. The iTip documentation calls this + // a party crasher. + $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array( + 'PARTSTAT' => $instances[$recurId] + )); + if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName; + } + unset($instances[$recurId]); + } + } + + if(!$masterObject) { + // No master object, we can't add new instances. + return null; + } + // If we got replies to instances that did not exist in the + // original list, it means that new exceptions must be created. + foreach($instances as $recurId=>$partstat) { + + $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); + $found = false; + $iterations = 1000; + do { + + $newObject = $recurrenceIterator->getEventObject(); + $recurrenceIterator->next(); + + if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) { + $found = true; + } + $iterations--; + + } while($recurrenceIterator->valid() && !$found && $iterations); + + // Invalid recurrence id. Skipping this object. + if (!$found) continue; + + unset( + $newObject->RRULE, + $newObject->EXDATE, + $newObject->RDATE + ); + $attendeeFound = false; + if (isset($newObject->ATTENDEE)) { + foreach($newObject->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $partstat; + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee + $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array( + 'PARTSTAT' => $partstat + )); + if ($itipMessage->senderName) { + $attendee['CN'] = $itipMessage->senderName; + } + } + $existingObject->add($newObject); + + } + return $existingObject; + + } + + /** + * This method is used in cases where an event got updated, and we + * potentially need to send emails to attendees to let them know of updates + * in the events. + * + * We will detect which attendees got added, which got removed and create + * specific messages for these situations. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * @return array + */ + protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) { + + // Merging attendee lists. + $attendees = array(); + foreach($oldEventInfo['attendees'] as $attendee) { + $attendees[$attendee['href']] = array( + 'href' => $attendee['href'], + 'oldInstances' => $attendee['instances'], + 'newInstances' => array(), + 'name' => $attendee['name'], + 'forceSend' => null, + ); + } + foreach($eventInfo['attendees'] as $attendee) { + if (isset($attendees[$attendee['href']])) { + $attendees[$attendee['href']]['name'] = $attendee['name']; + $attendees[$attendee['href']]['newInstances'] = $attendee['instances']; + $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; + } else { + $attendees[$attendee['href']] = array( + 'href' => $attendee['href'], + 'oldInstances' => array(), + 'newInstances' => $attendee['instances'], + 'name' => $attendee['name'], + 'forceSend' => $attendee['forceSend'], + ); + } + } + + $messages = array(); + + foreach($attendees as $attendee) { + + // An organizer can also be an attendee. We should not generate any + // messages for those. + if ($attendee['href']===$eventInfo['organizer']) { + continue; + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $eventInfo['organizer']; + $message->senderName = $eventInfo['organizerName']; + $message->recipient = $attendee['href']; + $message->recipientName = $attendee['name']; + + if (!$attendee['newInstances']) { + + // If there are no instances the attendee is a part of, it + // means the attendee was removed and we need to send him a + // CANCEL. + $message->method = 'CANCEL'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + $event = $icalMsg->add('VEVENT', array( + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + )); + if (isset($calendar->VEVENT->SUMMARY)) { + $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue()); + } + $event->add(clone $calendar->VEVENT->DTSTART); + if (isset($calendar->VEVENT->DTEND)) { + $event->add(clone $calendar->VEVENT->DTEND); + } elseif (isset($calendar->VEVENT->DURATION)) { + $event->add(clone $calendar->VEVENT->DURATION); + } + $org = $event->add('ORGANIZER', $eventInfo['organizer']); + if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName']; + $event->add('ATTENDEE', $attendee['href'], array( + 'CN' => $attendee['name'], + )); + $message->significantChange = true; + + } else { + + // The attendee gets the updated event body + $message->method = 'REQUEST'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + + foreach($calendar->select('VTIMEZONE') as $timezone) { + $icalMsg->add(clone $timezone); + } + + // We need to find out that this change is significant. If it's + // not, systems may opt to not send messages. + // + // We do this based on the 'significantChangeHash' which is + // some value that changes if there's a certain set of + // properties changed in the event, or simply if there's a + // difference in instances that the attendee is invited to. + + $message->significantChange = + $attendee['forceSend'] === 'REQUEST' || + array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || + $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash']; + + foreach($attendee['newInstances'] as $instanceId => $instanceInfo) { + + $currentEvent = clone $eventInfo['instances'][$instanceId]; + if ($instanceId === 'master') { + + // We need to find a list of events that the attendee + // is not a part of to add to the list of exceptions. + $exceptions = array(); + foreach($eventInfo['instances'] as $instanceId=>$vevent) { + if (!isset($attendee['newInstances'][$instanceId])) { + $exceptions[] = $instanceId; + } + } + + // If there were exceptions, we need to add it to an + // existing EXDATE property, if it exists. + if ($exceptions) { + if (isset($currentEvent->EXDATE)) { + $currentEvent->EXDATE->setParts(array_merge( + $currentEvent->EXDATE->getParts(), + $exceptions + )); + } else { + $currentEvent->EXDATE = $exceptions; + } + } + + // Cleaning up any scheduling information that + // shouldn't be sent along. + unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']); + unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']); + + foreach($currentEvent->ATTENDEE as $attendee) { + unset($attendee['SCHEDULE-FORCE-SEND']); + unset($attendee['SCHEDULE-STATUS']); + + // We're adding PARTSTAT=NEEDS-ACTION to ensure that + // iOS shows an "Inbox Item" + if (!isset($attendee['PARTSTAT'])) { + $attendee['PARTSTAT'] = 'NEEDS-ACTION'; + } + + } + + } + + $icalMsg->add($currentEvent); + + } + + } + + $message->message = $icalMsg; + $messages[] = $message; + + } + + return $messages; + + } + + /** + * Parse an event update for an attendee. + * + * This function figures out if we need to send a reply to an organizer. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * @param string $attendee + * @return Message[] + */ + protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { + + if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') { + return array(); + } + + // Don't bother generating messages for events that have already been + // cancelled. + if ($eventInfo['status']==='CANCELLED') { + return array(); + } + + $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? + $oldEventInfo['attendees'][$attendee]['instances'] : + array(); + + $instances = array(); + foreach($oldInstances as $instance) { + + $instances[$instance['id']] = array( + 'id' => $instance['id'], + 'oldstatus' => $instance['partstat'], + 'newstatus' => null, + ); + + } + foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) { + + if (isset($instances[$instance['id']])) { + $instances[$instance['id']]['newstatus'] = $instance['partstat']; + } else { + $instances[$instance['id']] = array( + 'id' => $instance['id'], + 'oldstatus' => null, + 'newstatus' => $instance['partstat'], + ); + } + + } + + // We need to also look for differences in EXDATE. If there are new + // items in EXDATE, it means that an attendee deleted instances of an + // event, which means we need to send DECLINED specifically for those + // instances. + // We only need to do that though, if the master event is not declined. + if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') { + foreach($eventInfo['exdate'] as $exDate) { + + if (!in_array($exDate, $oldEventInfo['exdate'])) { + if (isset($instances[$exDate])) { + $instances[$exDate]['newstatus'] = 'DECLINED'; + } else { + $instances[$exDate] = array( + 'id' => $exDate, + 'oldstatus' => null, + 'newstatus' => 'DECLINED', + ); + } + } + + } + } + + // Gathering a few extra properties for each instance. + foreach($instances as $recurId=>$instanceInfo) { + + if (isset($eventInfo['instances'][$recurId])) { + $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; + } else { + $instances[$recurId]['dtstart'] = $recurId; + } + + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->method = 'REPLY'; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $attendee; + $message->senderName = $eventInfo['attendees'][$attendee]['name']; + $message->recipient = $eventInfo['organizer']; + $message->recipientName = $eventInfo['organizerName']; + + $icalMsg = new VCalendar(); + $icalMsg->METHOD = 'REPLY'; + + $hasReply = false; + + foreach($instances as $instance) { + + if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') { + // Skip + continue; + } + + $event = $icalMsg->add('VEVENT', array( + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + )); + $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():''; + // Adding properties from the correct source instance + if (isset($eventInfo['instances'][$instance['id']])) { + $instanceObj = $eventInfo['instances'][$instance['id']]; + $event->add(clone $instanceObj->DTSTART); + if (isset($instanceObj->DTEND)) { + $event->add(clone $instanceObj->DTEND); + } elseif (isset($instanceObj->DURATION)) { + $event->add(clone $instanceObj->DURATION); + } + if (isset($instanceObj->SUMMARY)) { + $event->add('SUMMARY', $instanceObj->SUMMARY->getValue()); + } elseif ($summary) { + $event->add('SUMMARY', $summary); + } + } else { + // This branch of the code is reached, when a reply is + // generated for an instance of a recurring event, through the + // fact that the instance has disappeared by showing up in + // EXDATE + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE')); + } else { + $recur = $event->add('DTSTART', $dt); + } + if ($summary) { + $event->add('SUMMARY', $summary); + } + } + if ($instance['id'] !== 'master') { + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE')); + } else { + $recur = $event->add('RECURRENCE-ID', $dt); + } + } + $organizer = $event->add('ORGANIZER', $message->recipient); + if ($message->recipientName) { + $organizer['CN'] = $message->recipientName; + } + $attendee = $event->add('ATTENDEE', $message->sender, array( + 'PARTSTAT' => $instance['newstatus'] + )); + if ($message->senderName) { + $attendee['CN'] = $message->senderName; + } + $hasReply = true; + + } + + if ($hasReply) { + $message->message = $icalMsg; + return array($message); + } else { + return array(); + } + + } + + /** + * Returns attendee information and information about instances of an + * event. + * + * Returns an array with the following keys: + * + * 1. uid + * 2. organizer + * 3. organizerName + * 4. organizerScheduleAgent + * 5. organizerForceSend + * 6. instances + * 7. attendees + * 8. sequence + * 9. exdate + * 10. timezone - strictly the timezone on which the recurrence rule is + * based on. + * 11. significantChangeHash + * 12. status + * @param VCalendar $calendar + * @return array + */ + protected function parseEventInfo(VCalendar $calendar = null) { + + $uid = null; + $organizer = null; + $organizerName = null; + $organizerForceSend = null; + $sequence = null; + $timezone = null; + $status = null; + $organizerScheduleAgent = 'SERVER'; + + $significantChangeHash = ''; + + // Now we need to collect a list of attendees, and which instances they + // are a part of. + $attendees = array(); + + $instances = array(); + $exdate = array(); + + foreach($calendar->VEVENT as $vevent) { + + if (is_null($uid)) { + $uid = $vevent->UID->getValue(); + } else { + if ($uid !== $vevent->UID->getValue()) { + throw new ITipException('If a calendar contained more than one event, they must have the same UID.'); + } + } + + if (!isset($vevent->DTSTART)) { + throw new ITipException('An event MUST have a DTSTART property.'); + } + + if (isset($vevent->ORGANIZER)) { + if (is_null($organizer)) { + $organizer = $vevent->ORGANIZER->getNormalizedValue(); + $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null; + } else { + if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) { + throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); + } + } + $organizerForceSend = + isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ? + strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) : + null; + $organizerScheduleAgent = + isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? + strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) : + 'SERVER'; + } + if (is_null($sequence) && isset($vevent->SEQUENCE)) { + $sequence = $vevent->SEQUENCE->getValue(); + } + if (isset($vevent->EXDATE)) { + foreach ($vevent->select('EXDATE') as $val) { + $exdate = array_merge($exdate, $val->getParts()); + } + sort($exdate); + } + if (isset($vevent->STATUS)) { + $status = strtoupper($vevent->STATUS->getValue()); + } + + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + if (is_null($timezone)) { + if ($recurId === 'master') { + $timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); + } else { + $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone(); + } + } + if(isset($vevent->ATTENDEE)) { + foreach($vevent->ATTENDEE as $attendee) { + + if ($this->scheduleAgentServerRules && + isset($attendee['SCHEDULE-AGENT']) && + strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT' + ) { + continue; + } + $partStat = + isset($attendee['PARTSTAT']) ? + strtoupper($attendee['PARTSTAT']) : + 'NEEDS-ACTION'; + + $forceSend = + isset($attendee['SCHEDULE-FORCE-SEND']) ? + strtoupper($attendee['SCHEDULE-FORCE-SEND']) : + null; + + + if (isset($attendees[$attendee->getNormalizedValue()])) { + $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array( + 'id' => $recurId, + 'partstat' => $partStat, + 'force-send' => $forceSend, + ); + } else { + $attendees[$attendee->getNormalizedValue()] = array( + 'href' => $attendee->getNormalizedValue(), + 'instances' => array( + $recurId => array( + 'id' => $recurId, + 'partstat' => $partStat, + ), + ), + 'name' => isset($attendee['CN'])?(string)$attendee['CN']:null, + 'forceSend' => $forceSend, + ); + } + + } + $instances[$recurId] = $vevent; + + } + + foreach($this->significantChangeProperties as $prop) { + if (isset($vevent->$prop)) { + $propertyValues = $vevent->select($prop); + + $significantChangeHash.=$prop.':'; + + if ($prop === 'EXDATE') { + + $significantChangeHash.= implode(',', $exdate).';'; + + } else { + + foreach($propertyValues as $val) { + $significantChangeHash.= $val->getValue().';'; + } + + } + } + } + + } + $significantChangeHash = md5($significantChangeHash); + + return compact( + 'uid', + 'organizer', + 'organizerName', + 'organizerScheduleAgent', + 'organizerForceSend', + 'instances', + 'attendees', + 'sequence', + 'exdate', + 'timezone', + 'significantChangeHash', + 'status' + ); + + } + +} diff --git a/vendor/sabre/vobject/lib/ITip/ITipException.php b/vendor/sabre/vobject/lib/ITip/ITipException.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/ITip/ITipException.php @@ -0,0 +1,15 @@ +scheduleStatus) { + + return false; + + } else { + + list($scheduleStatus) = explode(';', $this->scheduleStatus); + return $scheduleStatus; + + } + + } + +} diff --git a/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php b/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php @@ -0,0 +1,18 @@ +iterator)) + return $this->iterator; + + return new ElementList(array($this)); + + } + + /** + * Sets the overridden iterator + * + * Note that this is not actually part of the iterator interface + * + * @param ElementList $iterator + * @return void + */ + public function setIterator(ElementList $iterator) { + + $this->iterator = $iterator; + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on) + * 2 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + return array(); + + } + + /* }}} */ + + /* {{{ Countable interface */ + + /** + * Returns the number of elements + * + * @return int + */ + public function count() { + + $it = $this->getIterator(); + return $it->count(); + + } + + /* }}} */ + + /* {{{ ArrayAccess Interface */ + + + /** + * Checks if an item exists through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) { + + $iterator = $this->getIterator(); + return $iterator->offsetExists($offset); + + } + + /** + * Gets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @return mixed + */ + public function offsetGet($offset) { + + $iterator = $this->getIterator(); + return $iterator->offsetGet($offset); + + } + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) { + + $iterator = $this->getIterator(); + $iterator->offsetSet($offset,$value); + + // @codeCoverageIgnoreStart + // + // This method always throws an exception, so we ignore the closing + // brace + } + // @codeCoverageIgnoreEnd + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @return void + */ + public function offsetUnset($offset) { + + $iterator = $this->getIterator(); + $iterator->offsetUnset($offset); + + // @codeCoverageIgnoreStart + // + // This method always throws an exception, so we ignore the closing + // brace + } + // @codeCoverageIgnoreEnd + + /* }}} */ +} diff --git a/vendor/sabre/vobject/lib/Parameter.php b/vendor/sabre/vobject/lib/Parameter.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Parameter.php @@ -0,0 +1,373 @@ +name = strtoupper($name); + $this->root = $root; + if (is_null($name)) { + $this->noName = true; + $this->name = static::guessParameterNameByValue($value); + } + + // If guessParameterNameByValue() returns an empty string + // above, we're actually dealing with a parameter that has no value. + // In that case we have to move the value to the name. + if ($this->name === '') { + $this->noName = false; + $this->name = strtoupper($value); + } else { + $this->setValue($value); + } + + } + + /** + * Try to guess property name by value, can be used for vCard 2.1 nameless parameters. + * + * Figuring out what the name should have been. Note that a ton of + * these are rather silly in 2014 and would probably rarely be + * used, but we like to be complete. + * + * @param string $value + * @return string + */ + public static function guessParameterNameByValue($value) { + switch(strtoupper($value)) { + + // Encodings + case '7-BIT' : + case 'QUOTED-PRINTABLE' : + case 'BASE64' : + $name = 'ENCODING'; + break; + + // Common types + case 'WORK' : + case 'HOME' : + case 'PREF' : + + // Delivery Label Type + case 'DOM' : + case 'INTL' : + case 'POSTAL' : + case 'PARCEL' : + + // Telephone types + case 'VOICE' : + case 'FAX' : + case 'MSG' : + case 'CELL' : + case 'PAGER' : + case 'BBS' : + case 'MODEM' : + case 'CAR' : + case 'ISDN' : + case 'VIDEO' : + + // EMAIL types (lol) + case 'AOL' : + case 'APPLELINK' : + case 'ATTMAIL' : + case 'CIS' : + case 'EWORLD' : + case 'INTERNET' : + case 'IBMMAIL' : + case 'MCIMAIL' : + case 'POWERSHARE' : + case 'PRODIGY' : + case 'TLX' : + case 'X400' : + + // Photo / Logo format types + case 'GIF' : + case 'CGM' : + case 'WMF' : + case 'BMP' : + case 'DIB' : + case 'PICT' : + case 'TIFF' : + case 'PDF ': + case 'PS' : + case 'JPEG' : + case 'MPEG' : + case 'MPEG2' : + case 'AVI' : + case 'QTIME' : + + // Sound Digital Audio Type + case 'WAVE' : + case 'PCM' : + case 'AIFF' : + + // Key types + case 'X509' : + case 'PGP' : + $name = 'TYPE'; + break; + + // Value types + case 'INLINE' : + case 'URL' : + case 'CONTENT-ID' : + case 'CID' : + $name = 'VALUE'; + break; + + default: + $name = ''; + } + + return $name; + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * @return void + */ + public function setValue($value) { + + $this->value = $value; + + } + + /** + * Returns the current value + * + * This method will always return a string, or null. If there were multiple + * values, it will automatically concatinate them (separated by comma). + * + * @return string|null + */ + public function getValue() { + + if (is_array($this->value)) { + return implode(',' , $this->value); + } else { + return $this->value; + } + + } + + /** + * Sets multiple values for this parameter. + * + * @param array $value + * @return void + */ + public function setParts(array $value) { + + $this->value = $value; + + } + + /** + * Returns all values for this parameter. + * + * If there were no values, an empty array will be returned. + * + * @return array + */ + public function getParts() { + + if (is_array($this->value)) { + return $this->value; + } elseif (is_null($this->value)) { + return array(); + } else { + return array($this->value); + } + + } + + /** + * Adds a value to this parameter + * + * If the argument is specified as an array, all items will be added to the + * parameter value list. + * + * @param string|array $part + * @return void + */ + public function addValue($part) { + + if (is_null($this->value)) { + $this->value = $part; + } else { + $this->value = array_merge((array)$this->value, (array)$part); + } + + } + + /** + * Checks if this parameter contains the specified value. + * + * This is a case-insensitive match. It makes sense to call this for for + * instance the TYPE parameter, to see if it contains a keyword such as + * 'WORK' or 'FAX'. + * + * @param string $value + * @return bool + */ + public function has($value) { + + return in_array( + strtolower($value), + array_map('strtolower', (array)$this->value) + ); + + } + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() { + + $value = $this->getParts(); + + if (count($value)===0) { + return $this->name . '='; + } + + if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) { + + return implode(';', $value); + + } + + return $this->name . '=' . array_reduce( + $value, + function($out, $item) { + + if (!is_null($out)) $out.=','; + + // If there's no special characters in the string, we'll use the simple + // format. + // + // The list of special characters is defined as: + // + // Any character except CONTROL, DQUOTE, ";", ":", "," + // + // by the iCalendar spec: + // https://tools.ietf.org/html/rfc5545#section-3.1 + // + // And we add ^ to that because of: + // https://tools.ietf.org/html/rfc6868 + // + // But we've found that iCal (7.0, shipped with OSX 10.9) + // severaly trips on + characters not being quoted, so we + // added + as well. + if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { + return $out.$item; + } else { + // Enclosing in double-quotes, and using RFC6868 for encoding any + // special characters + $out.='"' . strtr( + $item, + array( + '^' => '^^', + "\n" => '^n', + '"' => '^\'', + ) + ) . '"'; + return $out; + } + + } + ); + + } + + /** + * This method returns an array, with the representation as it should be + * encoded in json. This is used to create jCard or jCal documents. + * + * @return array + */ + public function jsonSerialize() { + + return $this->value; + + } + + /** + * Called when this object is being cast to a string + * + * @return string + */ + public function __toString() { + + return (string)$this->getValue(); + + } + + /** + * Returns the iterator for this object + * + * @return ElementList + */ + public function getIterator() { + + if (!is_null($this->iterator)) + return $this->iterator; + + return $this->iterator = new ArrayObject((array)$this->value); + + } + +} diff --git a/vendor/sabre/vobject/lib/ParseException.php b/vendor/sabre/vobject/lib/ParseException.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/ParseException.php @@ -0,0 +1,13 @@ +setInput($input); + } + if (is_null($this->input)) { + throw new EofException('End of input stream, or no input supplied'); + } + + if (!is_null($options)) { + $this->options = $options; + } + + switch($this->input[0]) { + case 'vcalendar' : + $this->root = new VCalendar(array(), false); + break; + case 'vcard' : + $this->root = new VCard(array(), false); + break; + default : + throw new ParseException('The root component must either be a vcalendar, or a vcard'); + + } + foreach($this->input[1] as $prop) { + $this->root->add($this->parseProperty($prop)); + } + if (isset($this->input[2])) foreach($this->input[2] as $comp) { + $this->root->add($this->parseComponent($comp)); + } + + // Resetting the input so we can throw an feof exception the next time. + $this->input = null; + + return $this->root; + + } + + /** + * Parses a component + * + * @param array $jComp + * @return \Sabre\VObject\Component + */ + public function parseComponent(array $jComp) { + + // We can remove $self from PHP 5.4 onward. + $self = $this; + + $properties = array_map( + function($jProp) use ($self) { + return $self->parseProperty($jProp); + }, + $jComp[1] + ); + + if (isset($jComp[2])) { + + $components = array_map( + function($jComp) use ($self) { + return $self->parseComponent($jComp); + }, + $jComp[2] + ); + + } else $components = array(); + + return $this->root->createComponent( + $jComp[0], + array_merge($properties, $components), + $defaults = false + ); + + } + + /** + * Parses properties. + * + * @param array $jProp + * @return \Sabre\VObject\Property + */ + public function parseProperty(array $jProp) { + + list( + $propertyName, + $parameters, + $valueType + ) = $jProp; + + $propertyName = strtoupper($propertyName); + + // This is the default class we would be using if we didn't know the + // value type. We're using this value later in this function. + $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); + + $parameters = (array)$parameters; + + $value = array_slice($jProp, 3); + + $valueType = strtoupper($valueType); + + if (isset($parameters['group'])) { + $propertyName = $parameters['group'] . '.' . $propertyName; + unset($parameters['group']); + } + + $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType); + $prop->setJsonValue($value); + + // We have to do something awkward here. FlatText as well as Text + // represents TEXT values. We have to normalize these here. In the + // future we can get rid of FlatText once we're allowed to break BC + // again. + if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') { + $defaultPropertyClass = 'Sabre\VObject\Property\Text'; + } + + // If the value type we received (e.g.: TEXT) was not the default value + // type for the given property (e.g.: BDAY), we need to add a VALUE= + // parameter. + if ($defaultPropertyClass !== get_class($prop)) { + $prop["VALUE"] = $valueType; + } + + return $prop; + + } + + /** + * Sets the input data + * + * @param resource|string|array $input + * @return void + */ + public function setInput($input) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + if (is_string($input)) { + $input = json_decode($input); + } + $this->input = $input; + + } + +} diff --git a/vendor/sabre/vobject/lib/Parser/MimeDir.php b/vendor/sabre/vobject/lib/Parser/MimeDir.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/MimeDir.php @@ -0,0 +1,628 @@ +root = null; + if (!is_null($input)) { + + $this->setInput($input); + + } + + if (!is_null($options)) $this->options = $options; + + $this->parseDocument(); + + return $this->root; + + } + + /** + * Sets the input buffer. Must be a string or stream. + * + * @param resource|string $input + * @return void + */ + public function setInput($input) { + + // Resetting the parser + $this->lineIndex = 0; + $this->startLine = 0; + + if (is_string($input)) { + // Convering to a stream. + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $input); + rewind($stream); + $this->input = $stream; + } elseif (is_resource($input)) { + $this->input = $input; + } else { + throw new \InvalidArgumentException('This parser can only read from strings or streams.'); + } + + } + + /** + * Parses an entire document. + * + * @return void + */ + protected function parseDocument() { + + $line = $this->readLine(); + + // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). + // It's 0xEF 0xBB 0xBF in UTF-8 hex. + if ( 3 <= strlen($line) + && ord($line[0]) === 0xef + && ord($line[1]) === 0xbb + && ord($line[2]) === 0xbf) { + $line = substr($line, 3); + } + + switch(strtoupper($line)) { + case 'BEGIN:VCALENDAR' : + $class = isset(VCalendar::$componentMap['VCALENDAR']) + ? VCalendar::$componentMap[$name] + : 'Sabre\\VObject\\Component\\VCalendar'; + break; + case 'BEGIN:VCARD' : + $class = isset(VCard::$componentMap['VCARD']) + ? VCard::$componentMap['VCARD'] + : 'Sabre\\VObject\\Component\\VCard'; + break; + default : + throw new ParseException('This parser only supports VCARD and VCALENDAR files'); + } + + $this->root = new $class(array(), false); + + while(true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line,0,4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $this->root->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name!==$this->root->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"'); + } + + } + + /** + * Parses a line, and if it hits a component, it will also attempt to parse + * the entire component + * + * @param string $line Unfolded line + * @return Node + */ + protected function parseLine($line) { + + // Start of a new component + if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') { + + $component = $this->root->createComponent(substr($line,6), array(), false); + + while(true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line,0,4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $component->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name!==$component->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"'); + } + + return $component; + + } else { + + // Property reader + $property = $this->readProperty($line); + if (!$property) { + // Ignored line + return false; + } + return $property; + + } + + } + + /** + * We need to look ahead 1 line every time to see if we need to 'unfold' + * the next line. + * + * If that was not the case, we store it here. + * + * @var null|string + */ + protected $lineBuffer; + + /** + * The real current line number. + */ + protected $lineIndex = 0; + + /** + * In the case of unfolded lines, this property holds the line number for + * the start of the line. + * + * @var int + */ + protected $startLine = 0; + + /** + * Contains a 'raw' representation of the current line. + * + * @var string + */ + protected $rawLine; + + /** + * Reads a single line from the buffer. + * + * This method strips any newlines and also takes care of unfolding. + * + * @throws \Sabre\VObject\EofException + * @return string + */ + protected function readLine() { + + if (!is_null($this->lineBuffer)) { + $rawLine = $this->lineBuffer; + $this->lineBuffer = null; + } else { + do { + $eof = feof($this->input); + + $rawLine = fgets($this->input); + + if ($eof || (feof($this->input) && $rawLine===false)) { + throw new EofException('End of document reached prematurely'); + } + if ($rawLine === false) { + throw new ParseException('Error reading from input stream'); + } + $rawLine = rtrim($rawLine, "\r\n"); + } while ($rawLine === ''); // Skipping empty lines + $this->lineIndex++; + } + $line = $rawLine; + + $this->startLine = $this->lineIndex; + + // Looking ahead for folded lines. + while (true) { + + $nextLine = rtrim(fgets($this->input), "\r\n"); + $this->lineIndex++; + if (!$nextLine) { + break; + } + if ($nextLine[0] === "\t" || $nextLine[0] === " ") { + $line .= substr($nextLine, 1); + $rawLine .= "\n " . substr($nextLine, 1); + } else { + $this->lineBuffer = $nextLine; + break; + } + + } + $this->rawLine = $rawLine; + return $line; + + } + + /** + * Reads a property or component from a line. + * + * @return void + */ + protected function readProperty($line) { + + if ($this->options & self::OPTION_FORGIVING) { + $propNameToken = 'A-Z0-9\-\._\\/'; + } else { + $propNameToken = 'A-Z0-9\-\.'; + } + + $paramNameToken = 'A-Z0-9\-'; + $safeChar = '^";:,'; + $qSafeChar = '^"'; + + $regex = "/ + ^(?P [$propNameToken]+ ) (?=[;:]) # property name + | + (?<=:)(?P .+)$ # property value + | + ;(?P [$paramNameToken]+) (?=[=;:]) # parameter name + | + (=|,)(?P # parameter value + (?: [$safeChar]*) | + \"(?: [$qSafeChar]+)\" + ) (?=[;:,]) + /xi"; + + //echo $regex, "\n"; die(); + preg_match_all($regex, $line, $matches, PREG_SET_ORDER); + + $property = array( + 'name' => null, + 'parameters' => array(), + 'value' => null + ); + + $lastParam = null; + + /** + * Looping through all the tokens. + * + * Note that we are looping through them in reverse order, because if a + * sub-pattern matched, the subsequent named patterns will not show up + * in the result. + */ + foreach($matches as $match) { + + if (isset($match['paramValue'])) { + if ($match['paramValue'] && $match['paramValue'][0] === '"') { + $value = substr($match['paramValue'], 1, -1); + } else { + $value = $match['paramValue']; + } + + $value = $this->unescapeParam($value); + + if (is_null($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = $value; + } elseif (is_array($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam][] = $value; + } else { + $property['parameters'][$lastParam] = array( + $property['parameters'][$lastParam], + $value + ); + } + continue; + } + if (isset($match['paramName'])) { + $lastParam = strtoupper($match['paramName']); + if (!isset($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = null; + } + continue; + } + if (isset($match['propValue'])) { + $property['value'] = $match['propValue']; + continue; + } + if (isset($match['name']) && $match['name']) { + $property['name'] = strtoupper($match['name']); + continue; + } + + // @codeCoverageIgnoreStart + throw new \LogicException('This code should not be reachable'); + // @codeCoverageIgnoreEnd + + } + + if (is_null($property['value'])) { + $property['value'] = ''; + } + if (!$property['name']) { + if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { + return false; + } + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } + + // vCard 2.1 states that parameters may appear without a name, and only + // a value. We can deduce the value based on it's name. + // + // Our parser will get those as parameters without a value instead, so + // we're filtering these parameters out first. + $namedParameters = array(); + $namelessParameters = array(); + + foreach($property['parameters'] as $name=>$value) { + if (!is_null($value)) { + $namedParameters[$name] = $value; + } else { + $namelessParameters[] = $name; + } + } + + $propObj = $this->root->createProperty($property['name'], null, $namedParameters); + + foreach($namelessParameters as $namelessParameter) { + $propObj->add(null, $namelessParameter); + } + + if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') { + $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); + } else { + $propObj->setRawMimeDirValue($property['value']); + } + + return $propObj; + + } + + /** + * Unescapes a property value. + * + * vCard 2.1 says: + * * Semi-colons must be escaped in some property values, specifically + * ADR, ORG and N. + * * Semi-colons must be escaped in parameter values, because semi-colons + * are also use to separate values. + * * No mention of escaping backslashes with another backslash. + * * newlines are not escaped either, instead QUOTED-PRINTABLE is used to + * span values over more than 1 line. + * + * vCard 3.0 says: + * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be + * escaped, all time time. + * * Comma's are used for delimeters in multiple values + * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped, + * as in some properties semi-colon is used for separators. + * * Properties using semi-colons: N, ADR, GEO, ORG + * * Both ADR and N's individual parts may be broken up further with a + * comma. + * * Properties using commas: NICKNAME, CATEGORIES + * + * vCard 4.0 (rfc6350) says: + * * Commas must be escaped. + * * Semi-colons may be escaped, an unescaped semi-colon _may_ be a + * delimiter, depending on the property. + * * Backslashes must be escaped + * * Newlines must be escaped as either \N or \n. + * * Some compound properties may contain multiple parts themselves, so a + * comma within a semi-colon delimited property may also be unescaped + * to denote multiple parts _within_ the compound property. + * * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP. + * * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID. + * + * Even though the spec says that commas must always be escaped, the + * example for GEO in Section 6.5.2 seems to violate this. + * + * iCalendar 2.0 (rfc5545) says: + * * Commas or semi-colons may be used as delimiters, depending on the + * property. + * * Commas, semi-colons, backslashes, newline (\N or \n) are always + * escaped, unless they are delimiters. + * * Colons shall not be escaped. + * * Commas can be considered the 'default delimiter' and is described as + * the delimiter in cases where the order of the multiple values is + * insignificant. + * * Semi-colons are described as the delimiter for 'structured values'. + * They are specifically used in Semi-colons are used as a delimiter in + * REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however. + * + * Now for the parameters + * + * If delimiter is not set (null) this method will just return a string. + * If it's a comma or a semi-colon the string will be split on those + * characters, and always return an array. + * + * @param string $input + * @param string $delimiter + * @return string|string[] + */ + static public function unescapeValue($input, $delimiter = ';') { + + $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )'; + if ($delimiter) { + $regex .= ' | (' . $delimiter . ')'; + } + $regex .= ') #x'; + + $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + $resultArray = array(); + $result = ''; + + foreach($matches as $match) { + + switch ($match) { + case '\\\\' : + $result .='\\'; + break; + case '\N' : + case '\n' : + $result .="\n"; + break; + case '\;' : + $result .=';'; + break; + case '\,' : + $result .=','; + break; + case $delimiter : + $resultArray[] = $result; + $result = ''; + break; + default : + $result .= $match; + break; + + } + + } + + $resultArray[] = $result; + return $delimiter ? $resultArray : $result; + + } + + /** + * Unescapes a parameter value. + * + * vCard 2.1: + * * Does not mention a mechanism for this. In addition, double quotes + * are never used to wrap values. + * * This means that parameters can simply not contain colons or + * semi-colons. + * + * vCard 3.0 (rfc2425, rfc2426): + * * Parameters _may_ be surrounded by double quotes. + * * If this is not the case, semi-colon, colon and comma may simply not + * occur (the comma used for multiple parameter values though). + * * If it is surrounded by double-quotes, it may simply not contain + * double-quotes. + * * This means that a parameter can in no case encode double-quotes, or + * newlines. + * + * vCard 4.0 (rfc6350) + * * Behavior seems to be identical to vCard 3.0 + * + * iCalendar 2.0 (rfc5545) + * * Behavior seems to be identical to vCard 3.0 + * + * Parameter escaping mechanism (rfc6868) : + * * This rfc describes a new way to escape parameter values. + * * New-line is encoded as ^n + * * ^ is encoded as ^^. + * * " is encoded as ^' + * + * @param string $input + * @return void + */ + private function unescapeParam($input) { + + return + preg_replace_callback( + '#(\^(\^|n|\'))#', + function($matches) { + switch($matches[2]) { + case 'n' : + return "\n"; + case '^' : + return '^'; + case '\'' : + return '"'; + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + }, + $input + ); + } + + /** + * Gets the full quoted printable value. + * + * We need a special method for this, because newlines have both a meaning + * in vCards, and in QuotedPrintable. + * + * This method does not do any decoding. + * + * @return string + */ + private function extractQuotedPrintableValue() { + + // We need to parse the raw line again to get the start of the value. + // + // We are basically looking for the first colon (:), but we need to + // skip over the parameters first, as they may contain one. + $regex = '/^ + (?: [^:])+ # Anything but a colon + (?: "[^"]")* # A parameter in double quotes + : # start of the value we really care about + (.*)$ + /xs'; + + preg_match($regex, $this->rawLine, $matches); + + $value = $matches[1]; + // Removing the first whitespace character from every line. Kind of + // like unfolding, but we keep the newline. + $value = str_replace("\n ", "\n", $value); + + // Microsoft products don't always correctly fold lines, they may be + // missing a whitespace. So if 'forgiving' is turned on, we will take + // those as well. + if ($this->options & self::OPTION_FORGIVING) { + while(substr($value,-1) === '=') { + // Reading the line + $this->readLine(); + // Grabbing the raw form + $value.="\n" . $this->rawLine; + } + } + + return $value; + + } + +} diff --git a/vendor/sabre/vobject/lib/Parser/Parser.php b/vendor/sabre/vobject/lib/Parser/Parser.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/Parser.php @@ -0,0 +1,77 @@ +setInput($input); + } + $this->options = $options; + } + + /** + * This method starts the parsing process. + * + * If the input was not supplied during construction, it's possible to pass + * it here instead. + * + * If either input or options are not supplied, the defaults will be used. + * + * @param mixed $input + * @param int|null $options + * @return array + */ + abstract public function parse($input = null, $options = null); + + /** + * Sets the input data + * + * @param mixed $input + * @return void + */ + abstract public function setInput($input); + +} diff --git a/vendor/sabre/vobject/lib/Property.php b/vendor/sabre/vobject/lib/Property.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property.php @@ -0,0 +1,555 @@ +value syntax. + * + * @param Component $root The root document + * @param string $name + * @param string|array|null $value + * @param array $parameters List of parameters + * @param string $group The vcard property group + * @return void + */ + function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) { + + $this->name = $name; + $this->group = $group; + + $this->root = $root; + + foreach($parameters as $k=>$v) { + $this->add($k, $v); + } + + if (!is_null($value)) { + $this->setValue($value); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * @return void + */ + function setValue($value) { + + $this->value = $value; + + } + + /** + * Returns the current value. + * + * This method will always return a singular value. If this was a + * multi-value object, some decision will be made first on how to represent + * it as a string. + * + * To get the correct multi-value version, use getParts. + * + * @return string + */ + function getValue() { + + if (is_array($this->value)) { + if (count($this->value)==0) { + return null; + } elseif (count($this->value)===1) { + return $this->value[0]; + } else { + return $this->getRawMimeDirValue($this->value); + } + } else { + return $this->value; + } + + } + + /** + * Sets a multi-valued property. + * + * @param array $parts + * @return void + */ + function setParts(array $parts) { + + $this->value = $parts; + + } + + /** + * Returns a multi-valued property. + * + * This method always returns an array, if there was only a single value, + * it will still be wrapped in an array. + * + * @return array + */ + function getParts() { + + if (is_null($this->value)) { + return array(); + } elseif (is_array($this->value)) { + return $this->value; + } else { + return array($this->value); + } + + } + + /** + * Adds a new parameter, and returns the new item. + * + * If a parameter with same name already existed, the values will be + * combined. + * If nameless parameter is added, we try to guess it's name. + * + * @param string $name + * @param string|null|array $value + * @return Node + */ + function add($name, $value = null) { + $noName = false; + if ($name === null) { + $name = Parameter::guessParameterNameByValue($value); + $noName = true; + } + + if (isset($this->parameters[strtoupper($name)])) { + $this->parameters[strtoupper($name)]->addValue($value); + } + else { + $param = new Parameter($this->root, $name, $value); + $param->noName = $noName; + $this->parameters[$param->name] = $param; + } + } + + /** + * Returns an iterable list of children + * + * @return array + */ + function parameters() { + + return $this->parameters; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + abstract function getValueType(); + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + abstract function setRawMimeDirValue($val); + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + abstract function getRawMimeDirValue(); + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + function serialize() { + + $str = $this->name; + if ($this->group) $str = $this->group . '.' . $this->name; + + foreach($this->parameters as $param) { + + $str.=';' . $param->serialize(); + + } + + $str.=':' . $this->getRawMimeDirValue(); + + $out = ''; + while(strlen($str)>0) { + if (strlen($str)>75) { + $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); + } else { + $out.=$str . "\r\n"; + $str=''; + break; + } + } + + return $out; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return $this->getParts(); + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * @return void + */ + function setJsonValue(array $value) { + + if (count($value)===1) { + $this->setValue(reset($value)); + } else { + $this->setValue($value); + } + + } + + /** + * This method returns an array, with the representation as it should be + * encoded in json. This is used to create jCard or jCal documents. + * + * @return array + */ + function jsonSerialize() { + + $parameters = array(); + + foreach($this->parameters as $parameter) { + if ($parameter->name === 'VALUE') { + continue; + } + $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize(); + } + // In jCard, we need to encode the property-group as a separate 'group' + // parameter. + if ($this->group) { + $parameters['group'] = $this->group; + } + + return array_merge( + array( + strtolower($this->name), + (object)$parameters, + strtolower($this->getValueType()), + ), + $this->getJsonValue() + ); + } + + + /** + * Called when this object is being cast to a string. + * + * If the property only had a single value, you will get just that. In the + * case the property had multiple values, the contents will be escaped and + * combined with ,. + * + * @return string + */ + function __toString() { + + return (string)$this->getValue(); + + } + + /* ArrayAccess interface {{{ */ + + /** + * Checks if an array element exists + * + * @param mixed $name + * @return bool + */ + function offsetExists($name) { + + if (is_int($name)) return parent::offsetExists($name); + + $name = strtoupper($name); + + foreach($this->parameters as $parameter) { + if ($parameter->name == $name) return true; + } + return false; + + } + + /** + * Returns a parameter. + * + * If the parameter does not exist, null is returned. + * + * @param string $name + * @return Node + */ + function offsetGet($name) { + + if (is_int($name)) return parent::offsetGet($name); + $name = strtoupper($name); + + if (!isset($this->parameters[$name])) { + return null; + } + + return $this->parameters[$name]; + + } + + /** + * Creates a new parameter + * + * @param string $name + * @param mixed $value + * @return void + */ + function offsetSet($name, $value) { + + if (is_int($name)) { + parent::offsetSet($name, $value); + // @codeCoverageIgnoreStart + // This will never be reached, because an exception is always + // thrown. + return; + // @codeCoverageIgnoreEnd + } + + $param = new Parameter($this->root, $name, $value); + $this->parameters[$param->name] = $param; + + } + + /** + * Removes one or more parameters with the specified name + * + * @param string $name + * @return void + */ + function offsetUnset($name) { + + if (is_int($name)) { + parent::offsetUnset($name); + // @codeCoverageIgnoreStart + // This will never be reached, because an exception is always + // thrown. + return; + // @codeCoverageIgnoreEnd + } + + unset($this->parameters[strtoupper($name)]); + + } + /* }}} */ + + /** + * This method is automatically called when the object is cloned. + * Specifically, this will ensure all child elements are also cloned. + * + * @return void + */ + function __clone() { + + foreach($this->parameters as $key=>$child) { + $this->parameters[$key] = clone $child; + $this->parameters[$key]->parent = $this; + } + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * - Node::REPAIR - If something is broken, and automatic repair may + * be attempted. + * + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @param int $options + * @return array + */ + function validate($options = 0) { + + $warnings = array(); + + // Checking if our value is UTF-8 + if (!StringUtil::isUTF8($this->getRawMimeDirValue())) { + + $oldValue = $this->getRawMimeDirValue(); + $level = 3; + if ($options & self::REPAIR) { + $newValue = StringUtil::convertToUTF8($oldValue); + if (true || StringUtil::isUTF8($newValue)) { + $this->setRawMimeDirValue($newValue); + $level = 1; + } + + } + + + if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) { + $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')'; + } else { + $message = 'Property is not valid UTF-8! ' . $oldValue; + } + + $warnings[] = array( + 'level' => $level, + 'message' => $message, + 'node' => $this, + ); + } + + // Checking if the propertyname does not contain any invalid bytes. + if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed', + 'node' => $this, + ); + if ($options & self::REPAIR) { + // Uppercasing and converting underscores to dashes. + $this->name = strtoupper( + str_replace('_', '-', $this->name) + ); + // Removing every other invalid character + $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name); + + } + + } + + if ($encoding = $this->offsetGet('ENCODING')) { + + if ($this->root->getDocumentType()===Document::VCARD40) { + $warnings[] = array( + 'level' => 1, + 'message' => 'ENCODING parameter is not valid in vCard 4.', + 'node' => $this + ); + } else { + + $encoding = (string)$encoding; + + $allowedEncoding = array(); + + switch($this->root->getDocumentType()) { + case Document::ICALENDAR20 : + $allowedEncoding = array('8BIT', 'BASE64'); + break; + case Document::VCARD21 : + $allowedEncoding = array('QUOTED-PRINTABLE', 'BASE64', '8BIT'); + break; + case Document::VCARD30 : + $allowedEncoding = array('B'); + break; + + } + if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) { + $warnings[] = array( + 'level' => 1, + 'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.', + 'node' => $this + ); + } + } + + } + + // Validating inner parameters + foreach($this->parameters as $param) { + $warnings = array_merge($warnings, $param->validate($options)); + } + + return $warnings; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/Binary.php b/vendor/sabre/vobject/lib/Property/Binary.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Binary.php @@ -0,0 +1,127 @@ +value = $value[0]; + } else { + throw new \InvalidArgumentException('The argument must either be a string or an array with only one child'); + } + + } else { + + $this->value = $value; + + } + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + public function setRawMimeDirValue($val) { + + $this->value = base64_decode($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return base64_encode($this->value); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return 'BINARY'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + return array(base64_encode($this->getValue())); + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * @return void + */ + public function setJsonValue(array $value) { + + $value = array_map('base64_decode', $value); + parent::setJsonValue($value); + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/Boolean.php b/vendor/sabre/vobject/lib/Property/Boolean.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Boolean.php @@ -0,0 +1,63 @@ +setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return $this->value?'TRUE':'FALSE'; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return 'BOOLEAN'; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/FlatText.php b/vendor/sabre/vobject/lib/Property/FlatText.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/FlatText.php @@ -0,0 +1,49 @@ +setValue($val); + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/FloatValue.php b/vendor/sabre/vobject/lib/Property/FloatValue.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/FloatValue.php @@ -0,0 +1,104 @@ +delimiter, $val); + foreach($val as &$item) { + $item = (float)$item; + } + $this->setParts($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return implode( + $this->delimiter, + $this->getParts() + ); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "FLOAT"; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + $val = array_map( + function($item) { + + return (float)$item; + + }, + $this->getParts() + ); + + // Special-casing the GEO property. + // + // See: + // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2 + if ($this->name==='GEO') { + return array($val); + } else { + return $val; + } + + } +} diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php b/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php @@ -0,0 +1,61 @@ +getValue(); + if (!strpos($input, ':')) { + return $input; + } + list($schema, $everythingElse) = explode(':', $input, 2); + return strtolower($schema) . ':' . $everythingElse; + + } +} diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Date.php b/vendor/sabre/vobject/lib/Property/ICalendar/Date.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/ICalendar/Date.php @@ -0,0 +1,18 @@ +setDateTimes($parts); + } else { + parent::setParts($parts); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * Instead of strings, you may also use DateTime here. + * + * @param string|array|\DateTime $value + * @return void + */ + public function setValue($value) { + + if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) { + $this->setDateTimes($value); + } elseif ($value instanceof \DateTime) { + $this->setDateTimes(array($value)); + } else { + parent::setValue($value); + } + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + public function setRawMimeDirValue($val) { + + $this->setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns true if this is a DATE-TIME value, false if it's a DATE. + * + * @return bool + */ + public function hasTime() { + + return strtoupper((string)$this['VALUE']) !== 'DATE'; + + } + + /** + * Returns true if this is a floating DATE or DATE-TIME. + * + * Note that DATE is always floating. + */ + public function isFloating() { + + return + !$this->hasTime() || + ( + !isset($this['TZID']) && + strpos($this->getValue(),'Z')===false + ); + + } + + /** + * Returns a date-time value. + * + * Note that if this property contained more than 1 date-time, only the + * first will be returned. To get an array with multiple values, call + * getDateTimes. + * + * If no timezone information is known, because it's either an all-day + * property or floating time, we will use the DateTimeZone argument to + * figure out the exact date. + * + * @param DateTimeZone $timeZone + * @return \DateTime + */ + public function getDateTime(DateTimeZone $timeZone = null) { + + $dt = $this->getDateTimes($timeZone); + if (!$dt) return null; + + return $dt[0]; + + } + + /** + * Returns multiple date-time values. + * + * If no timezone information is known, because it's either an all-day + * property or floating time, we will use the DateTimeZone argument to + * figure out the exact date. + * + * @param DateTimeZone $timeZone + * @return \DateTime[] + */ + public function getDateTimes(DateTimeZone $timeZone = null) { + + // Does the property have a TZID? + $tzid = $this['TZID']; + + if ($tzid) { + $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); + } + + $dts = array(); + foreach($this->getParts() as $part) { + $dts[] = DateTimeParser::parse($part, $timeZone); + } + return $dts; + + } + + /** + * Sets the property as a DateTime object. + * + * @param \DateTime $dt + * @param bool isFloating If set to true, timezones will be ignored. + * @return void + */ + public function setDateTime(\DateTime $dt, $isFloating = false) { + + $this->setDateTimes(array($dt), $isFloating); + + } + + /** + * Sets the property as multiple date-time objects. + * + * The first value will be used as a reference for the timezones, and all + * the otehr values will be adjusted for that timezone + * + * @param \DateTime[] $dt + * @param bool isFloating If set to true, timezones will be ignored. + * @return void + */ + public function setDateTimes(array $dt, $isFloating = false) { + + $values = array(); + + if($this->hasTime()) { + + $tz = null; + $isUtc = false; + + foreach($dt as $d) { + + if ($isFloating) { + $values[] = $d->format('Ymd\\THis'); + continue; + } + if (is_null($tz)) { + $tz = $d->getTimeZone(); + $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z')); + if (!$isUtc) { + $this->offsetSet('TZID', $tz->getName()); + } + } else { + $d->setTimeZone($tz); + } + + if ($isUtc) { + $values[] = $d->format('Ymd\\THis\\Z'); + } else { + $values[] = $d->format('Ymd\\THis'); + } + + } + if ($isUtc || $isFloating) { + $this->offsetUnset('TZID'); + } + + } else { + + foreach($dt as $d) { + + $values[] = $d->format('Ymd'); + + } + $this->offsetUnset('TZID'); + + } + + $this->value = $values; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return $this->hasTime()?'DATE-TIME':'DATE'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + $dts = $this->getDateTimes(); + $hasTime = $this->hasTime(); + $isFloating = $this->isFloating(); + + $tz = $dts[0]->getTimeZone(); + $isUtc = $isFloating ? false : in_array($tz->getName() , array('UTC', 'GMT', 'Z')); + + return array_map( + function($dt) use ($hasTime, $isUtc) { + + if ($hasTime) { + return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':''); + } else { + return $dt->format('Y-m-d'); + } + + }, + $dts + ); + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * @return void + */ + public function setJsonValue(array $value) { + + // dates and times in jCal have one difference to dates and times in + // iCalendar. In jCal date-parts are separated by dashes, and + // time-parts are separated by colons. It makes sense to just remove + // those. + $this->setValue( + array_map( + function($item) { + + return strtr($item, array(':'=>'', '-'=>'')); + + }, + $value + ) + ); + + } + /** + * We need to intercept offsetSet, because it may be used to alter the + * VALUE from DATE-TIME to DATE or vice-versa. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function offsetSet($name, $value) { + + parent::offsetSet($name, $value); + if (strtoupper($name)!=='VALUE') { + return; + } + + // This will ensure that dates are correctly encoded. + $this->setDateTimes($this->getDateTimes()); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on) + * 2 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + $messages = parent::validate($options); + $valueType = $this->getValueType(); + $values = $this->getParts(); + try { + foreach($values as $value) { + switch($valueType) { + case 'DATE' : + $foo = DateTimeParser::parseDate($value); + break; + case 'DATE-TIME' : + $foo = DateTimeParser::parseDateTime($value); + break; + } + } + } catch (\LogicException $e) { + $messages[] = array( + 'level' => 3, + 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, + 'node' => $this, + ); + } + return $messages; + + } +} diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php b/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php @@ -0,0 +1,86 @@ +setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return 'DURATION'; + + } + + /** + * Returns a DateInterval representation of the Duration property. + * + * If the property has more than one value, only the first is returned. + * + * @return \DateInterval + */ + public function getDateInterval() { + + $parts = $this->getParts(); + $value = $parts[0]; + return DateTimeParser::parseDuration($value); + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Period.php b/vendor/sabre/vobject/lib/Property/ICalendar/Period.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/ICalendar/Period.php @@ -0,0 +1,129 @@ +setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "PERIOD"; + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * @return void + */ + public function setJsonValue(array $value) { + + $value = array_map( + function($item) { + + return strtr(implode('/', $item), array(':' => '', '-' => '')); + + }, + $value + ); + parent::setJsonValue($value); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + $return = array(); + foreach($this->getParts() as $item) { + + list($start, $end) = explode('/', $item, 2); + + $start = DateTimeParser::parseDateTime($start); + + // This is a duration value. + if ($end[0]==='P') { + $return[] = array( + $start->format('Y-m-d\\TH:i:s'), + $end + ); + } else { + $end = DateTimeParser::parseDateTime($end); + $return[] = array( + $start->format('Y-m-d\\TH:i:s'), + $end->format('Y-m-d\\TH:i:s'), + ); + } + + } + + return $return; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php b/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php @@ -0,0 +1,203 @@ +value array that is accessible using + * getParts, and may be set using setParts. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Recur extends Property { + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * @return void + */ + public function setValue($value) { + + // If we're getting the data from json, we'll be receiving an object + if ($value instanceof \StdClass) { + $value = (array)$value; + } + + if (is_array($value)) { + $newVal = array(); + foreach($value as $k=>$v) { + + if (is_string($v)) { + $v = strtoupper($v); + + // The value had multiple sub-values + if (strpos($v,',')!==false) { + $v = explode(',', $v); + } + } else { + $v = array_map('strtoupper', $v); + } + + $newVal[strtoupper($k)] = $v; + } + $this->value = $newVal; + } elseif (is_string($value)) { + $this->value = self::stringToArray($value); + } else { + throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); + } + + } + + /** + * Returns the current value. + * + * This method will always return a singular value. If this was a + * multi-value object, some decision will be made first on how to represent + * it as a string. + * + * To get the correct multi-value version, use getParts. + * + * @return string + */ + public function getValue() { + + $out = array(); + foreach($this->value as $key=>$value) { + $out[] = $key . '=' . (is_array($value)?implode(',', $value):$value); + } + return strtoupper(implode(';',$out)); + + } + + /** + * Sets a multi-valued property. + * + * @param array $parts + * @return void + */ + public function setParts(array $parts) { + + $this->setValue($parts); + + } + + /** + * Returns a multi-valued property. + * + * This method always returns an array, if there was only a single value, + * it will still be wrapped in an array. + * + * @return array + */ + public function getParts() { + + return $this->value; + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + public function setRawMimeDirValue($val) { + + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return $this->getValue(); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "RECUR"; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + $values = array(); + foreach($this->getParts() as $k=>$v) { + $values[strtolower($k)] = $v; + } + return array($values); + + } + + /** + * Parses an RRULE value string, and turns it into a struct-ish array. + * + * @param string $value + * @return array + */ + static function stringToArray($value) { + + $value = strtoupper($value); + $newValue = array(); + foreach(explode(';', $value) as $part) { + + // Skipping empty parts. + if (empty($part)) { + continue; + } + list($partName, $partValue) = explode('=', $part); + + // The value itself had multiple values.. + if (strpos($partValue,',')!==false) { + $partValue=explode(',', $partValue); + } + $newValue[$partName] = $partValue; + + } + + return $newValue; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/IntegerValue.php b/vendor/sabre/vobject/lib/Property/IntegerValue.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/IntegerValue.php @@ -0,0 +1,72 @@ +setValue((int)$val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return $this->value; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "INTEGER"; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + return array((int)$this->getValue()); + + } +} diff --git a/vendor/sabre/vobject/lib/Property/Text.php b/vendor/sabre/vobject/lib/Property/Text.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Text.php @@ -0,0 +1,333 @@ + 5, + 'ADR' => 7, + ); + + /** + * Creates the property. + * + * You can specify the parameters either in key=>value syntax, in which case + * parameters will automatically be created, or you can just pass a list of + * Parameter objects. + * + * @param Component $root The root document + * @param string $name + * @param string|array|null $value + * @param array $parameters List of parameters + * @param string $group The vcard property group + * @return void + */ + public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) { + + // There's two types of multi-valued text properties: + // 1. multivalue properties. + // 2. structured value properties + // + // The former is always separated by a comma, the latter by semi-colon. + if (in_array($name, $this->structuredValues)) { + $this->delimiter = ';'; + } + + parent::__construct($root, $name, $value, $parameters, $group); + + } + + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + public function setRawMimeDirValue($val) { + + $this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); + + } + + /** + * Sets the value as a quoted-printable encoded string. + * + * @param string $val + * @return void + */ + public function setQuotedPrintableValue($val) { + + $val = quoted_printable_decode($val); + + // Quoted printable only appears in vCard 2.1, and the only character + // that may be escaped there is ;. So we are simply splitting on just + // that. + // + // We also don't have to unescape \\, so all we need to look for is a ; + // that's not preceeded with a \. + $regex = '# (?setValue($matches); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + $val = $this->getParts(); + + if (isset($this->minimumPropertyValues[$this->name])) { + $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); + } + + foreach($val as &$item) { + + if (!is_array($item)) { + $item = array($item); + } + + foreach($item as &$subItem) { + $subItem = strtr( + $subItem, + array( + '\\' => '\\\\', + ';' => '\;', + ',' => '\,', + "\n" => '\n', + "\r" => "", + ) + ); + } + $item = implode(',', $item); + + } + + return implode($this->delimiter, $val); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + // Structured text values should always be returned as a single + // array-item. Multi-value text should be returned as multiple items in + // the top-array. + if (in_array($this->name, $this->structuredValues)) { + return array($this->getParts()); + } else { + return $this->getParts(); + } + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "TEXT"; + + } + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() { + + // We need to kick in a special type of encoding, if it's a 2.1 vcard. + if ($this->root->getDocumentType() !== Document::VCARD21) { + return parent::serialize(); + } + + $val = $this->getParts(); + + if (isset($this->minimumPropertyValues[$this->name])) { + $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); + } + + // Imploding multiple parts into a single value, and splitting the + // values with ;. + if (count($val)>1) { + foreach($val as $k=>$v) { + $val[$k] = str_replace(';','\;', $v); + } + $val = implode(';', $val); + } else { + $val = $val[0]; + } + + $str = $this->name; + if ($this->group) $str = $this->group . '.' . $this->name; + foreach($this->parameters as $param) { + + if ($param->getValue() === 'QUOTED-PRINTABLE') { + continue; + } + $str.=';' . $param->serialize(); + + } + + + + // If the resulting value contains a \n, we must encode it as + // quoted-printable. + if (strpos($val,"\n") !== false) { + + $str.=';ENCODING=QUOTED-PRINTABLE:'; + $lastLine=$str; + $out = null; + + // The PHP built-in quoted-printable-encode does not correctly + // encode newlines for us. Specifically, the \r\n sequence must in + // vcards be encoded as =0D=OA and we must insert soft-newlines + // every 75 bytes. + for($ii=0;$ii= 32 && $ord <=126) { + $lastLine.=$val[$ii]; + } else { + $lastLine.='=' . strtoupper(bin2hex($val[$ii])); + } + if (strlen($lastLine)>=75) { + // Soft line break + $out.=$lastLine. "=\r\n "; + $lastLine = null; + } + + } + if (!is_null($lastLine)) $out.= $lastLine . "\r\n"; + return $out; + + } else { + $str.=':' . $val; + $out = ''; + while(strlen($str)>0) { + if (strlen($str)>75) { + $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); + } else { + $out.=$str . "\r\n"; + $str=''; + break; + } + } + + return $out; + + + } + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * - Node::REPAIR - If something is broken, and automatic repair may + * be attempted. + * + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + $warnings = parent::validate($options); + + if (isset($this->minimumPropertyValues[$this->name])) { + + $minimum = $this->minimumPropertyValues[$this->name]; + $parts = $this->getParts(); + if (count($parts) < $minimum) { + $warnings[] = array( + 'level' => 1, + 'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts), + 'node' => $this, + ); + if ($options & self::REPAIR) { + $parts = array_pad($parts, $minimum, ''); + $this->setParts($parts); + } + } + + } + return $warnings; + + } +} diff --git a/vendor/sabre/vobject/lib/Property/Time.php b/vendor/sabre/vobject/lib/Property/Time.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Time.php @@ -0,0 +1,94 @@ +getValue()); + + $timeStr = ''; + + // Hour + if (!is_null($parts['hour'])) { + $timeStr.=$parts['hour']; + + if (!is_null($parts['minute'])) { + $timeStr.=':'; + } + } else { + // We know either minute or second _must_ be set, so we insert a + // dash for an empty value. + $timeStr.='-'; + } + + // Minute + if (!is_null($parts['minute'])) { + $timeStr.=$parts['minute']; + + if (!is_null($parts['second'])) { + $timeStr.=':'; + } + } else { + if (isset($parts['second'])) { + // Dash for empty minute + $timeStr.='-'; + } + } + + // Second + if (!is_null($parts['second'])) { + $timeStr.=$parts['second']; + } + + // Timezone + if (!is_null($parts['timezone'])) { + $timeStr.=$parts['timezone']; + } + + return array($timeStr); + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/Unknown.php b/vendor/sabre/vobject/lib/Property/Unknown.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Unknown.php @@ -0,0 +1,50 @@ +getRawMimeDirValue()); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "UNKNOWN"; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/Uri.php b/vendor/sabre/vobject/lib/Property/Uri.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/Uri.php @@ -0,0 +1,95 @@ +name === 'URL') { + $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x'; + $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $newVal = ''; + foreach($matches as $match) { + switch($match) { + case '\:' : + $newVal.=':'; + break; + default : + $newVal.=$match; + break; + } + } + $this->value = $newVal; + } else { + $this->value = $val; + } + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + if (is_array($this->value)) { + return $this->value[0]; + } else { + return $this->value; + } + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/UtcOffset.php b/vendor/sabre/vobject/lib/Property/UtcOffset.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/UtcOffset.php @@ -0,0 +1,37 @@ +value = $dt->format('Ymd'); + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php b/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php @@ -0,0 +1,317 @@ +1) { + throw new \InvalidArgumentException('Only one value allowed'); + } + if (isset($parts[0]) && $parts[0] instanceof \DateTime) { + $this->setDateTime($parts[0]); + } else { + parent::setParts($parts); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * Instead of strings, you may also use DateTime here. + * + * @param string|array|\DateTime $value + * @return void + */ + public function setValue($value) { + + if ($value instanceof \DateTime) { + $this->setDateTime($value); + } else { + parent::setValue($value); + } + + } + + /** + * Sets the property as a DateTime object. + * + * @param \DateTime $dt + * @return void + */ + public function setDateTime(\DateTime $dt) { + + $values = array(); + + $tz = null; + $isUtc = false; + + $tz = $dt->getTimeZone(); + $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z')); + + if ($isUtc) { + $value = $dt->format('Ymd\\THis\\Z'); + } else { + // Calculating the offset. + $value = $dt->format('Ymd\\THisO'); + } + + $this->value = $value; + + } + + /** + * Returns a date-time value. + * + * Note that if this property contained more than 1 date-time, only the + * first will be returned. To get an array with multiple values, call + * getDateTimes. + * + * If no time was specified, we will always use midnight (in the default + * timezone) as the time. + * + * If parts of the date were omitted, such as the year, we will grab the + * current values for those. So at the time of writing, if the year was + * omitted, we would have filled in 2014. + * + * @return \DateTime + */ + public function getDateTime() { + + $dts = array(); + $now = new DateTime(); + + $tzFormat = $now->getTimezone()->getOffset($now)===0?'\\Z':'O'; + $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat)); + + $value = $this->getValue(); + + $dateParts = DateTimeParser::parseVCardDateTime($this->getValue()); + + // This sets all the missing parts to the current date/time. + // So if the year was missing for a birthday, we're making it 'this + // year'. + foreach($dateParts as $k=>$v) { + if (is_null($v)) { + $dateParts[$k] = $nowParts[$k]; + } + } + return new DateTime("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]"); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + public function getJsonValue() { + + $parts = DateTimeParser::parseVCardDateTime($this->getValue()); + + $dateStr = ''; + + // Year + if (!is_null($parts['year'])) { + $dateStr.=$parts['year']; + + if (!is_null($parts['month'])) { + // If a year and a month is set, we need to insert a separator + // dash. + $dateStr.='-'; + } + + } else { + + if (!is_null($parts['month']) || !is_null($parts['date'])) { + // Inserting two dashes + $dateStr.='--'; + } + + } + + // Month + + if (!is_null($parts['month'])) { + $dateStr.=$parts['month']; + + if (isset($parts['date'])) { + // If month and date are set, we need the separator dash. + $dateStr.='-'; + } + } else { + if (isset($parts['date'])) { + // If the month is empty, and a date is set, we need a 'empty + // dash' + $dateStr.='-'; + } + } + + // Date + if (!is_null($parts['date'])) { + $dateStr.=$parts['date']; + } + + + // Early exit if we don't have a time string. + if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) { + return array($dateStr); + } + + $dateStr.='T'; + + // Hour + if (!is_null($parts['hour'])) { + $dateStr.=$parts['hour']; + + if (!is_null($parts['minute'])) { + $dateStr.=':'; + } + } else { + // We know either minute or second _must_ be set, so we insert a + // dash for an empty value. + $dateStr.='-'; + } + + // Minute + if (!is_null($parts['minute'])) { + $dateStr.=$parts['minute']; + + if (!is_null($parts['second'])) { + $dateStr.=':'; + } + } else { + if (isset($parts['second'])) { + // Dash for empty minute + $dateStr.='-'; + } + } + + // Second + if (!is_null($parts['second'])) { + $dateStr.=$parts['second']; + } + + // Timezone + if (!is_null($parts['timezone'])) { + $dateStr.=$parts['timezone']; + } + + return array($dateStr); + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * @return void + */ + public function setRawMimeDirValue($val) { + + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * This method returns an array with detected problems. + * Every element has the following properties: + * + * * level - problem level. + * * message - A human-readable string describing the issue. + * * node - A reference to the problematic node. + * + * The level means: + * 1 - The issue was repaired (only happens if REPAIR was turned on) + * 2 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + $messages = parent::validate($options); + $value = $this->getValue(); + try { + DateTimeParser::parseVCardDateTime($value); + } catch (\InvalidArgumentException $e) { + $messages[] = array( + 'level' => 3, + 'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property', + 'node' => $this, + ); + } + return $messages; + + } +} diff --git a/vendor/sabre/vobject/lib/Property/VCard/DateTime.php b/vendor/sabre/vobject/lib/Property/VCard/DateTime.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/VCard/DateTime.php @@ -0,0 +1,33 @@ +setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + public function getRawMimeDirValue() { + + return $this->getValue(); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() { + + return "LANGUAGE-TAG"; + + } + +} diff --git a/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php b/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php @@ -0,0 +1,69 @@ +getValue()); + + $dateStr = + $parts['year'] . '-' . + $parts['month'] . '-' . + $parts['date'] . 'T' . + $parts['hour'] . ':' . + $parts['minute'] . ':' . + $parts['second']; + + // Timezone + if (!is_null($parts['timezone'])) { + $dateStr.=$parts['timezone']; + } + + return array($dateStr); + + } +} diff --git a/vendor/sabre/vobject/lib/Reader.php b/vendor/sabre/vobject/lib/Reader.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Reader.php @@ -0,0 +1,73 @@ +parse($data, $options); + + return $result; + + } + + /** + * Parses a jCard or jCal object, and returns the top component. + * + * The options argument is a bitfield. Pass any of the OPTIONS constant to + * alter the parsers' behaviour. + * + * You can either a string, a readable stream, or an array for it's input. + * Specifying the array is useful if json_decode was already called on the + * input. + * + * @param string|resource|array $data + * @param int $options + * @return Node + */ + static public function readJson($data, $options = 0) { + + $parser = new Parser\Json(); + $result = $parser->parse($data, $options); + + return $result; + + } + +} diff --git a/vendor/sabre/vobject/lib/Recur/EventIterator.php b/vendor/sabre/vobject/lib/Recur/EventIterator.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Recur/EventIterator.php @@ -0,0 +1,496 @@ +timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->timeZone = $timeZone; + + if (is_array($input)) { + $events = $input; + } elseif ($input instanceof VEvent) { + // Single instance mode. + $events = array($input); + } else { + // Calendar + UID mode. + $uid = (string)$uid; + if (!$uid) { + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); + } + if (!isset($input->VEVENT)) { + throw new InvalidArgumentException('No events found in this calendar'); + } + $events = $input->getByUID($uid); + + } + + foreach($events as $vevent) { + + if (!isset($vevent->{'RECURRENCE-ID'})) { + + $this->masterEvent = $vevent; + + } else { + + $this->exceptions[ + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() + ] = true; + $this->overriddenEvents[] = $vevent; + + } + + } + + if (!$this->masterEvent) { + // No base event was found. CalDAV does allow cases where only + // overridden instances are stored. + // + // In this particular case, we're just going to grab the first + // event and use that instead. This may not always give the + // desired result. + if (!count($this->overriddenEvents)) { + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); + } + $this->masterEvent = array_shift($this->overriddenEvents); + } + + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); + + if (isset($this->masterEvent->EXDATE)) { + + foreach($this->masterEvent->EXDATE as $exDate) { + + foreach($exDate->getDateTimes($this->timeZone) as $dt) { + $this->exceptions[$dt->getTimeStamp()] = true; + } + + } + + } + + if (isset($this->masterEvent->DTEND)) { + $this->eventDuration = + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - + $this->startDate->getTimeStamp(); + } elseif (isset($this->masterEvent->DURATION)) { + $duration = $this->masterEvent->DURATION->getDateInterval(); + $end = clone $this->startDate; + $end->add($duration); + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); + } elseif ($this->allDay) { + $this->eventDuration = 3600 * 24; + } else { + $this->eventDuration = 0; + } + + if (isset($this->masterEvent->RDATE)) { + $this->recurIterator = new RDateIterator( + $this->masterEvent->RDATE->getParts(), + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { + $this->recurIterator = new RRuleIterator( + $this->masterEvent->RRULE->getParts(), + $this->startDate + ); + } else { + $this->recurIterator = new RRuleIterator( + array( + 'FREQ' => 'DAILY', + 'COUNT' => 1, + ), + $this->startDate + ); + } + + $this->rewind(); + if (!$this->valid()) { + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); + } + + } + + /** + * Returns the date for the current position of the iterator. + * + * @return DateTime + */ + public function current() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the start date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtStart() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the end date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtEnd() { + + if (!$this->valid()) { + return null; + } + $end = clone $this->currentDate; + $end->modify('+' . $this->eventDuration . ' seconds'); + return $end; + + } + + /** + * Returns a VEVENT for the current iterations of the event. + * + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND + * altered. + * + * @return VEvent + */ + public function getEventObject() { + + if ($this->currentOverriddenEvent) { + return $this->currentOverriddenEvent; + } + + $event = clone $this->masterEvent; + + // Ignoring the following block, because PHPUnit's code coverage + // ignores most of these lines, and this messes with our stats. + // + // @codeCoverageIgnoreStart + unset( + $event->RRULE, + $event->EXDATE, + $event->RDATE, + $event->EXRULE, + $event->{'RECURRENCE-ID'} + ); + // @codeCoverageIgnoreEnd + + $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); + } + $recurid = clone $event->DTSTART; + $recurid->name = 'RECURRENCE-ID'; + $event->add($recurid); + return $event; + + } + + /** + * Returns the current position of the iterator. + * + * This is for us simply a 0-based index. + * + * @return int + */ + public function key() { + + // The counter is always 1 ahead. + return $this->counter - 1; + + } + + /** + * This is called after next, to see if the iterator is still at a valid + * position, or if it's at the end. + * + * @return bool + */ + public function valid() { + + return !!$this->currentDate; + + } + + /** + * Sets the iterator back to the starting point. + */ + public function rewind() { + + $this->recurIterator->rewind(); + // re-creating overridden event index. + $index = array(); + foreach($this->overriddenEvents as $key=>$event) { + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); + $index[$stamp] = $key; + } + krsort($index); + $this->counter = 0; + $this->overriddenEventsIndex = $index; + $this->currentOverriddenEvent = null; + + $this->nextDate = null; + $this->currentDate = clone $this->startDate; + + $this->next(); + + } + + /** + * Advances the iterator with one step. + * + * @return void + */ + public function next() { + + $this->currentOverriddenEvent = null; + $this->counter++; + if ($this->nextDate) { + // We had a stored value. + $nextDate = $this->nextDate; + $this->nextDate = null; + } else { + // We need to ask rruleparser for the next date. + // We need to do this until we find a date that's not in the + // exception list. + do { + if (!$this->recurIterator->valid()) { + $nextDate = null; + break; + } + $nextDate = $this->recurIterator->current(); + $this->recurIterator->next(); + } while(isset($this->exceptions[$nextDate->getTimeStamp()])); + + } + + + // $nextDate now contains what rrule thinks is the next one, but an + // overridden event may cut ahead. + if ($this->overriddenEventsIndex) { + + $offset = end($this->overriddenEventsIndex); + $timestamp = key($this->overriddenEventsIndex); + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { + // Overridden event comes first. + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; + + // Putting the rrule next date aside. + $this->nextDate = $nextDate; + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); + + // Ensuring that this item will only be used once. + array_pop($this->overriddenEventsIndex); + + // Exit point! + return; + + } + + } + + $this->currentDate = $nextDate; + + } + + /** + * Quickly jump to a date in the future. + * + * @param DateTime $dateTime + */ + public function fastForward(DateTime $dateTime) { + + while($this->valid() && $this->getDtEnd() < $dateTime ) { + $this->next(); + } + + } + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + public function isInfinite() { + + return $this->recurIterator->isInfinite(); + + } + + /** + * RRULE parser + * + * @var RRuleIterator + */ + protected $recurIterator; + + /** + * The duration, in seconds, of the master event. + * + * We use this to calculate the DTEND for subsequent events. + */ + protected $eventDuration; + + /** + * A reference to the main (master) event. + * + * @var VEVENT + */ + protected $masterEvent; + + /** + * List of overridden events. + * + * @var array + */ + protected $overriddenEvents = array(); + + /** + * Overridden event index. + * + * Key is timestamp, value is the index of the item in the $overriddenEvent + * property. + * + * @var array + */ + protected $overriddenEventsIndex; + + /** + * A list of recurrence-id's that are either part of EXDATE, or are + * overridden. + * + * @var array + */ + protected $exceptions = array(); + + /** + * Internal event counter + * + * @var int + */ + protected $counter; + + /** + * The very start of the iteration process. + * + * @var DateTime + */ + protected $startDate; + + /** + * Where we are currently in the iteration process + * + * @var DateTime + */ + protected $currentDate; + + /** + * The next date from the rrule parser. + * + * Sometimes we need to temporary store the next date, because an + * overridden event came before. + * + * @var DateTime + */ + protected $nextDate; + +} diff --git a/vendor/sabre/vobject/lib/Recur/NoInstancesException.php b/vendor/sabre/vobject/lib/Recur/NoInstancesException.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Recur/NoInstancesException.php @@ -0,0 +1,18 @@ +startDate = $start; + $this->parseRDate($rrule); + $this->currentDate = clone $this->startDate; + + } + + /* Implementation of the Iterator interface {{{ */ + + public function current() { + + if (!$this->valid()) return null; + return clone $this->currentDate; + + } + + /** + * Returns the current item number. + * + * @return int + */ + public function key() { + + return $this->counter; + + } + + /** + * Returns whether the current item is a valid item for the recurrence + * iterator. + * + * @return bool + */ + public function valid() { + + return ($this->counter <= count($this->dates)); + + } + + /** + * Resets the iterator. + * + * @return void + */ + public function rewind() { + + $this->currentDate = clone $this->startDate; + $this->counter = 0; + + } + + /** + * Goes on to the next iteration. + * + * @return void + */ + public function next() { + + $this->counter++; + if (!$this->valid()) return; + + $this->currentDate = + DateTimeParser::parse( + $this->dates[$this->counter-1] + ); + + } + + /* End of Iterator implementation }}} */ + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + public function isInfinite() { + + return false; + + } + + /** + * This method allows you to quickly go to the next occurrence after the + * specified date. + * + * @param DateTime $dt + * @return void + */ + public function fastForward(\DateTime $dt) { + + while($this->valid() && $this->currentDate < $dt ) { + $this->next(); + } + + } + + /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + * + * @var DateTime + */ + protected $startDate; + + /** + * The date of the current iteration. You can get this by calling + * ->current(). + * + * @var DateTime + */ + protected $currentDate; + + /** + * The current item in the list. + * + * You can get this number with the key() method. + * + * @var int + */ + protected $counter = 0; + + /* }}} */ + + /** + * This method receives a string from an RRULE property, and populates this + * class with all the values. + * + * @param string|array $rrule + * @return void + */ + protected function parseRDate($rdate) { + + if (is_string($rdate)) { + $rdate = explode(',', $rdate); + } + + $this->dates = $rdate; + + } + +} diff --git a/vendor/sabre/vobject/lib/Recur/RRuleIterator.php b/vendor/sabre/vobject/lib/Recur/RRuleIterator.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Recur/RRuleIterator.php @@ -0,0 +1,904 @@ +startDate = $start; + $this->parseRRule($rrule); + $this->currentDate = clone $this->startDate; + + } + + /* Implementation of the Iterator interface {{{ */ + + public function current() { + + if (!$this->valid()) return null; + return clone $this->currentDate; + + } + + /** + * Returns the current item number + * + * @return int + */ + public function key() { + + return $this->counter; + + } + + /** + * Returns whether the current item is a valid item for the recurrence + * iterator. This will return false if we've gone beyond the UNTIL or COUNT + * statements. + * + * @return bool + */ + public function valid() { + + if (!is_null($this->count)) { + return $this->counter < $this->count; + } + return is_null($this->until) || $this->currentDate <= $this->until; + + } + + /** + * Resets the iterator + * + * @return void + */ + public function rewind() { + + $this->currentDate = clone $this->startDate; + $this->counter = 0; + + } + + /** + * Goes on to the next iteration + * + * @return void + */ + public function next() { + + $previousStamp = $this->currentDate->getTimeStamp(); + + // Otherwise, we find the next event in the normal RRULE + // sequence. + switch($this->frequency) { + + case 'hourly' : + $this->nextHourly(); + break; + + case 'daily' : + $this->nextDaily(); + break; + + case 'weekly' : + $this->nextWeekly(); + break; + + case 'monthly' : + $this->nextMonthly(); + break; + + case 'yearly' : + $this->nextYearly(); + break; + + } + $this->counter++; + + } + + /* End of Iterator implementation }}} */ + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + public function isInfinite() { + + return !$this->count && !$this->until; + + } + + /** + * This method allows you to quickly go to the next occurrence after the + * specified date. + * + * @param DateTime $dt + * @return void + */ + public function fastForward(\DateTime $dt) { + + while($this->valid() && $this->currentDate < $dt ) { + $this->next(); + } + + } + + /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + * + * @var DateTime + */ + protected $startDate; + + /** + * The date of the current iteration. You can get this by calling + * ->current(). + * + * @var DateTime + */ + protected $currentDate; + + /** + * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly, + * yearly. + * + * @var string + */ + protected $frequency; + + /** + * The number of recurrences, or 'null' if infinitely recurring. + * + * @var int + */ + protected $count; + + /** + * The interval. + * + * If for example frequency is set to daily, interval = 2 would mean every + * 2 days. + * + * @var int + */ + protected $interval = 1; + + /** + * The last instance of this recurrence, inclusively + * + * @var \DateTime|null + */ + protected $until; + + /** + * Which seconds to recur. + * + * This is an array of integers (between 0 and 60) + * + * @var array + */ + protected $bySecond; + + /** + * Which minutes to recur + * + * This is an array of integers (between 0 and 59) + * + * @var array + */ + protected $byMinute; + + /** + * Which hours to recur + * + * This is an array of integers (between 0 and 23) + * + * @var array + */ + protected $byHour; + + /** + * The current item in the list. + * + * You can get this number with the key() method. + * + * @var int + */ + protected $counter = 0; + + /** + * Which weekdays to recur. + * + * This is an array of weekdays + * + * This may also be preceeded by a positive or negative integer. If present, + * this indicates the nth occurrence of a specific day within the monthly or + * yearly rrule. For instance, -2TU indicates the second-last tuesday of + * the month, or year. + * + * @var array + */ + protected $byDay; + + /** + * Which days of the month to recur + * + * This is an array of days of the months (1-31). The value can also be + * negative. -5 for instance means the 5th last day of the month. + * + * @var array + */ + protected $byMonthDay; + + /** + * Which days of the year to recur. + * + * This is an array with days of the year (1 to 366). The values can also + * be negative. For instance, -1 will always represent the last day of the + * year. (December 31st). + * + * @var array + */ + protected $byYearDay; + + /** + * Which week numbers to recur. + * + * This is an array of integers from 1 to 53. The values can also be + * negative. -1 will always refer to the last week of the year. + * + * @var array + */ + protected $byWeekNo; + + /** + * Which months to recur. + * + * This is an array of integers from 1 to 12. + * + * @var array + */ + protected $byMonth; + + /** + * Which items in an existing st to recur. + * + * These numbers work together with an existing by* rule. It specifies + * exactly which items of the existing by-rule to filter. + * + * Valid values are 1 to 366 and -1 to -366. As an example, this can be + * used to recur the last workday of the month. + * + * This would be done by setting frequency to 'monthly', byDay to + * 'MO,TU,WE,TH,FR' and bySetPos to -1. + * + * @var array + */ + protected $bySetPos; + + /** + * When the week starts. + * + * @var string + */ + protected $weekStart = 'MO'; + + /* Functions that advance the iterator {{{ */ + + /** + * Does the processing for advancing the iterator for hourly frequency. + * + * @return void + */ + protected function nextHourly() { + + $this->currentDate->modify('+' . $this->interval . ' hours'); + + } + + /** + * Does the processing for advancing the iterator for daily frequency. + * + * @return void + */ + protected function nextDaily() { + + if (!$this->byHour && !$this->byDay) { + $this->currentDate->modify('+' . $this->interval . ' days'); + return; + } + + if (isset($this->byHour)) { + $recurrenceHours = $this->getHours(); + } + + if (isset($this->byDay)) { + $recurrenceDays = $this->getDays(); + } + + if (isset($this->byMonth)) { + $recurrenceMonths = $this->getMonths(); + } + + do { + if ($this->byHour) { + if ($this->currentDate->format('G') == '23') { + // to obey the interval rule + $this->currentDate->modify('+' . $this->interval-1 . ' days'); + } + + $this->currentDate->modify('+1 hours'); + + } else { + $this->currentDate->modify('+' . $this->interval . ' days'); + + } + + // Current month of the year + $currentMonth = $this->currentDate->format('n'); + + // Current day of the week + $currentDay = $this->currentDate->format('w'); + + // Current hour of the day + $currentHour = $this->currentDate->format('G'); + + } while ( + ($this->byDay && !in_array($currentDay, $recurrenceDays)) || + ($this->byHour && !in_array($currentHour, $recurrenceHours)) || + ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) + ); + + } + + /** + * Does the processing for advancing the iterator for weekly frequency. + * + * @return void + */ + protected function nextWeekly() { + + if (!$this->byHour && !$this->byDay) { + $this->currentDate->modify('+' . $this->interval . ' weeks'); + return; + } + + if ($this->byHour) { + $recurrenceHours = $this->getHours(); + } + + if ($this->byDay) { + $recurrenceDays = $this->getDays(); + } + + // First day of the week: + $firstDay = $this->dayMap[$this->weekStart]; + + do { + + if ($this->byHour) { + $this->currentDate->modify('+1 hours'); + } else { + $this->currentDate->modify('+1 days'); + } + + // Current day of the week + $currentDay = (int) $this->currentDate->format('w'); + + // Current hour of the day + $currentHour = (int) $this->currentDate->format('G'); + + // We need to roll over to the next week + if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) { + $this->currentDate->modify('+' . $this->interval-1 . ' weeks'); + + // We need to go to the first day of this week, but only if we + // are not already on this first day of this week. + if($this->currentDate->format('w') != $firstDay) { + $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]); + } + } + + // We have a match + } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours))); + } + + /** + * Does the processing for advancing the iterator for monthly frequency. + * + * @return void + */ + protected function nextMonthly() { + + $currentDayOfMonth = $this->currentDate->format('j'); + if (!$this->byMonthDay && !$this->byDay) { + + // If the current day is higher than the 28th, rollover can + // occur to the next month. We Must skip these invalid + // entries. + if ($currentDayOfMonth < 29) { + $this->currentDate->modify('+' . $this->interval . ' months'); + } else { + $increase = 0; + do { + $increase++; + $tempDate = clone $this->currentDate; + $tempDate->modify('+ ' . ($this->interval*$increase) . ' months'); + } while ($tempDate->format('j') != $currentDayOfMonth); + $this->currentDate = $tempDate; + } + return; + } + + while(true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach($occurrences as $occurrence) { + + // The first occurrence thats higher than the current + // day of the month wins. + if ($occurrence > $currentDayOfMonth) { + break 2; + } + + } + + // If we made it all the way here, it means there were no + // valid occurrences, and we need to advance to the next + // month. + // + // This line does not currently work in hhvm. Temporary workaround + // follows: + // $this->currentDate->modify('first day of this month'); + $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); + // end of workaround + $this->currentDate->modify('+ ' . $this->interval . ' months'); + + // This goes to 0 because we need to start counting at the + // beginning. + $currentDayOfMonth = 0; + + } + + $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence); + + } + + /** + * Does the processing for advancing the iterator for yearly frequency. + * + * @return void + */ + protected function nextYearly() { + + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + // No sub-rules, so we just advance by year + if (!$this->byMonth) { + + // Unless it was a leap day! + if ($currentMonth==2 && $currentDayOfMonth==29) { + + $counter = 0; + do { + $counter++; + // Here we increase the year count by the interval, until + // we hit a date that's also in a leap year. + // + // We could just find the next interval that's dividable by + // 4, but that would ignore the rule that there's no leap + // year every year that's dividable by a 100, but not by + // 400. (1800, 1900, 2100). So we just rely on the datetime + // functions instead. + $nextDate = clone $this->currentDate; + $nextDate->modify('+ ' . ($this->interval*$counter) . ' years'); + } while ($nextDate->format('n')!=2); + $this->currentDate = $nextDate; + + return; + + } + + // The easiest form + $this->currentDate->modify('+' . $this->interval . ' years'); + return; + + } + + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + $advancedToNewMonth = false; + + // If we got a byDay or getMonthDay filter, we must first expand + // further. + if ($this->byDay || $this->byMonthDay) { + + while(true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach($occurrences as $occurrence) { + + // The first occurrence that's higher than the current + // day of the month wins. + // If we advanced to the next month or year, the first + // occurrence is always correct. + if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { + break 2; + } + + } + + // If we made it here, it means we need to advance to + // the next month or year. + $currentDayOfMonth = 1; + $advancedToNewMonth = true; + do { + + $currentMonth++; + if ($currentMonth>12) { + $currentYear+=$this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + + $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + + } + + // If we made it here, it means we got a valid occurrence + $this->currentDate->setDate($currentYear, $currentMonth, $occurrence); + return; + + } else { + + // These are the 'byMonth' rules, if there are no byDay or + // byMonthDay sub-rules. + do { + + $currentMonth++; + if ($currentMonth>12) { + $currentYear+=$this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + + return; + + } + + } + + /* }}} */ + + /** + * This method receives a string from an RRULE property, and populates this + * class with all the values. + * + * @param string|array $rrule + * @return void + */ + protected function parseRRule($rrule) { + + if (is_string($rrule)) { + $rrule = Property\ICalendar\Recur::stringToArray($rrule); + } + + foreach($rrule as $key=>$value) { + + $key = strtoupper($key); + switch($key) { + + case 'FREQ' : + $value = strtolower($value); + if (!in_array( + $value, + array('secondly','minutely','hourly','daily','weekly','monthly','yearly') + )) { + throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); + } + $this->frequency = $value; + break; + + case 'UNTIL' : + $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone()); + + // In some cases events are generated with an UNTIL= + // parameter before the actual start of the event. + // + // Not sure why this is happening. We assume that the + // intention was that the event only recurs once. + // + // So we are modifying the parameter so our code doesn't + // break. + if($this->until < $this->startDate) { + $this->until = $this->startDate; + } + break; + + case 'INTERVAL' : + // No break + + case 'COUNT' : + $val = (int)$value; + if ($val < 1) { + throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!'); + } + $key = strtolower($key); + $this->$key = $val; + break; + + case 'BYSECOND' : + $this->bySecond = (array)$value; + break; + + case 'BYMINUTE' : + $this->byMinute = (array)$value; + break; + + case 'BYHOUR' : + $this->byHour = (array)$value; + break; + + case 'BYDAY' : + $value = (array)$value; + foreach($value as $part) { + if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { + throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part); + } + } + $this->byDay = $value; + break; + + case 'BYMONTHDAY' : + $this->byMonthDay = (array)$value; + break; + + case 'BYYEARDAY' : + $this->byYearDay = (array)$value; + break; + + case 'BYWEEKNO' : + $this->byWeekNo = (array)$value; + break; + + case 'BYMONTH' : + $this->byMonth = (array)$value; + break; + + case 'BYSETPOS' : + $this->bySetPos = (array)$value; + break; + + case 'WKST' : + $this->weekStart = strtoupper($value); + break; + + default: + throw new \InvalidArgumentException('Not supported: ' . strtoupper($key)); + + } + + } + + } + + /** + * Mappings between the day number and english day name. + * + * @var array + */ + protected $dayNames = array( + 0 => 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + ); + + /** + * Returns all the occurrences for a monthly frequency with a 'byDay' or + * 'byMonthDay' expansion for the current month. + * + * The returned list is an array of integers with the day of month (1-31). + * + * @return array + */ + protected function getMonthlyOccurrences() { + + $startDate = clone $this->currentDate; + + $byDayResults = array(); + + // Our strategy is to simply go through the byDays, advance the date to + // that point and add it to the results. + if ($this->byDay) foreach($this->byDay as $day) { + + $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]]; + + + // Dayname will be something like 'wednesday'. Now we need to find + // all wednesdays in this month. + $dayHits = array(); + + // workaround for missing 'first day of the month' support in hhvm + $checkDate = new \DateTime($startDate->format('Y-m-1')); + // workaround modify always advancing the date even if the current day is a $dayName in hhvm + if ($checkDate->format('l') !== $dayName) { + $checkDate->modify($dayName); + } + + do { + $dayHits[] = $checkDate->format('j'); + $checkDate->modify('next ' . $dayName); + } while ($checkDate->format('n') === $startDate->format('n')); + + // So now we have 'all wednesdays' for month. It is however + // possible that the user only really wanted the 1st, 2nd or last + // wednesday. + if (strlen($day)>2) { + $offset = (int)substr($day,0,-2); + + if ($offset>0) { + // It is possible that the day does not exist, such as a + // 5th or 6th wednesday of the month. + if (isset($dayHits[$offset-1])) { + $byDayResults[] = $dayHits[$offset-1]; + } + } else { + + // if it was negative we count from the end of the array + // might not exist, fx. -5th tuesday + if (isset($dayHits[count($dayHits) + $offset])) { + $byDayResults[] = $dayHits[count($dayHits) + $offset]; + } + } + } else { + // There was no counter (first, second, last wednesdays), so we + // just need to add the all to the list). + $byDayResults = array_merge($byDayResults, $dayHits); + + } + + } + + $byMonthDayResults = array(); + if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) { + + // Removing values that are out of range for this month + if ($monthDay > $startDate->format('t') || + $monthDay < 0-$startDate->format('t')) { + continue; + } + if ($monthDay>0) { + $byMonthDayResults[] = $monthDay; + } else { + // Negative values + $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; + } + } + + // If there was just byDay or just byMonthDay, they just specify our + // (almost) final list. If both were provided, then byDay limits the + // list. + if ($this->byMonthDay && $this->byDay) { + $result = array_intersect($byMonthDayResults, $byDayResults); + } elseif ($this->byMonthDay) { + $result = $byMonthDayResults; + } else { + $result = $byDayResults; + } + $result = array_unique($result); + sort($result, SORT_NUMERIC); + + // The last thing that needs checking is the BYSETPOS. If it's set, it + // means only certain items in the set survive the filter. + if (!$this->bySetPos) { + return $result; + } + + $filteredResult = array(); + foreach($this->bySetPos as $setPos) { + + if ($setPos<0) { + $setPos = count($result)+($setPos+1); + } + if (isset($result[$setPos-1])) { + $filteredResult[] = $result[$setPos-1]; + } + } + + sort($filteredResult, SORT_NUMERIC); + return $filteredResult; + + } + + /** + * Simple mapping from iCalendar day names to day numbers + * + * @var array + */ + protected $dayMap = array( + 'SU' => 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6, + ); + + protected function getHours() + { + $recurrenceHours = array(); + foreach($this->byHour as $byHour) { + $recurrenceHours[] = $byHour; + } + + return $recurrenceHours; + } + + protected function getDays() { + + $recurrenceDays = array(); + foreach($this->byDay as $byDay) { + + // The day may be preceeded with a positive (+n) or + // negative (-n) integer. However, this does not make + // sense in 'weekly' so we ignore it here. + $recurrenceDays[] = $this->dayMap[substr($byDay,-2)]; + + } + + return $recurrenceDays; + } + + protected function getMonths() { + + $recurrenceMonths = array(); + foreach($this->byMonth as $byMonth) { + $recurrenceMonths[] = $byMonth; + } + + return $recurrenceMonths; + } +} diff --git a/vendor/sabre/vobject/lib/RecurrenceIterator.php b/vendor/sabre/vobject/lib/RecurrenceIterator.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/RecurrenceIterator.php @@ -0,0 +1,21 @@ +children() as $component) { + if (!$component instanceof VObject\Component) { + continue; + } + + // Get all timezones + if ($component->name === 'VTIMEZONE') { + $this->vtimezones[(string)$component->TZID] = $component; + continue; + } + + // Get component UID for recurring Events search + if(!$component->UID) { + $component->UID = sha1(microtime()) . '-vobjectimport'; + } + $uid = (string)$component->UID; + + // Take care of recurring events + if (!array_key_exists($uid, $this->objects)) { + $this->objects[$uid] = new VCalendar(); + } + + $this->objects[$uid]->add(clone $component); + } + + } + + /** + * Every time getNext() is called, a new object will be parsed, until we + * hit the end of the stream. + * + * When the end is reached, null will be returned. + * + * @return Sabre\VObject\Component|null + */ + public function getNext() { + + if($object=array_shift($this->objects)) { + + // create our baseobject + $object->version = '2.0'; + $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN'; + $object->calscale = 'GREGORIAN'; + + // add vtimezone information to obj (if we have it) + foreach ($this->vtimezones as $vtimezone) { + $object->add($vtimezone); + } + + return $object; + + } else { + + return null; + + } + + } + +} diff --git a/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php b/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php @@ -0,0 +1,39 @@ +input = $input; + $this->parser = new MimeDir($input, $options); + + } + + /** + * Every time getNext() is called, a new object will be parsed, until we + * hit the end of the stream. + * + * When the end is reached, null will be returned. + * + * @return Sabre\VObject\Component|null + */ + public function getNext() { + + try { + $object = $this->parser->parse(); + + if (!$object instanceof VObject\Component\VCard) { + throw new VObject\ParseException('The supplied input contained non-VCARD data.'); + } + + } catch (VObject\EofException $e) { + return null; + } + + return $object; + + } + +} diff --git a/vendor/sabre/vobject/lib/StringUtil.php b/vendor/sabre/vobject/lib/StringUtil.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/StringUtil.php @@ -0,0 +1,65 @@ + 'UTC', + 31 => 'Africa/Casablanca', + + // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. + // I'm not even kidding.. We handle this special case in the + // getTimeZone method. + 2 => 'Europe/Lisbon', + 1 => 'Europe/London', + 4 => 'Europe/Berlin', + 6 => 'Europe/Prague', + 3 => 'Europe/Paris', + 69 => 'Africa/Luanda', // This was a best guess + 7 => 'Europe/Athens', + 5 => 'Europe/Bucharest', + 49 => 'Africa/Cairo', + 50 => 'Africa/Harare', + 59 => 'Europe/Helsinki', + 27 => 'Asia/Jerusalem', + 26 => 'Asia/Baghdad', + 74 => 'Asia/Kuwait', + 51 => 'Europe/Moscow', + 56 => 'Africa/Nairobi', + 25 => 'Asia/Tehran', + 24 => 'Asia/Muscat', // Best guess + 54 => 'Asia/Baku', + 48 => 'Asia/Kabul', + 58 => 'Asia/Yekaterinburg', + 47 => 'Asia/Karachi', + 23 => 'Asia/Calcutta', + 62 => 'Asia/Kathmandu', + 46 => 'Asia/Almaty', + 71 => 'Asia/Dhaka', + 66 => 'Asia/Colombo', + 61 => 'Asia/Rangoon', + 22 => 'Asia/Bangkok', + 64 => 'Asia/Krasnoyarsk', + 45 => 'Asia/Shanghai', + 63 => 'Asia/Irkutsk', + 21 => 'Asia/Singapore', + 73 => 'Australia/Perth', + 75 => 'Asia/Taipei', + 20 => 'Asia/Tokyo', + 72 => 'Asia/Seoul', + 70 => 'Asia/Yakutsk', + 19 => 'Australia/Adelaide', + 44 => 'Australia/Darwin', + 18 => 'Australia/Brisbane', + 76 => 'Australia/Sydney', + 43 => 'Pacific/Guam', + 42 => 'Australia/Hobart', + 68 => 'Asia/Vladivostok', + 41 => 'Asia/Magadan', + 17 => 'Pacific/Auckland', + 40 => 'Pacific/Fiji', + 67 => 'Pacific/Tongatapu', + 29 => 'Atlantic/Azores', + 53 => 'Atlantic/Cape_Verde', + 30 => 'America/Noronha', + 8 => 'America/Sao_Paulo', // Best guess + 32 => 'America/Argentina/Buenos_Aires', + 60 => 'America/Godthab', + 28 => 'America/St_Johns', + 9 => 'America/Halifax', + 33 => 'America/Caracas', + 65 => 'America/Santiago', + 35 => 'America/Bogota', + 10 => 'America/New_York', + 34 => 'America/Indiana/Indianapolis', + 55 => 'America/Guatemala', + 11 => 'America/Chicago', + 37 => 'America/Mexico_City', + 36 => 'America/Edmonton', + 38 => 'America/Phoenix', + 12 => 'America/Denver', // Best guess + 13 => 'America/Los_Angeles', // Best guess + 14 => 'America/Anchorage', + 15 => 'Pacific/Honolulu', + 16 => 'Pacific/Midway', + 39 => 'Pacific/Kwajalein', + ); + + /** + * This method will try to find out the correct timezone for an iCalendar + * date-time value. + * + * You must pass the contents of the TZID parameter, as well as the full + * calendar. + * + * If the lookup fails, this method will return the default PHP timezone + * (as configured using date_default_timezone_set, or the date.timezone ini + * setting). + * + * Alternatively, if $failIfUncertain is set to true, it will throw an + * exception if we cannot accurately determine the timezone. + * + * @param string $tzid + * @param Sabre\VObject\Component $vcalendar + * @return DateTimeZone + */ + static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { + + // First we will just see if the tzid is a support timezone identifier. + // + // The only exception is if the timezone starts with (. This is to + // handle cases where certain microsoft products generate timezone + // identifiers that for instance look like: + // + // (GMT+01.00) Sarajevo/Warsaw/Zagreb + // + // Since PHP 5.5.10, the first bit will be used as the timezone and + // this method will return just GMT+01:00. This is wrong, because it + // doesn't take DST into account. + if ($tzid[0]!=='(') { + + // PHP has a bug that logs PHP warnings even it shouldn't: + // https://bugs.php.net/bug.php?id=67881 + // + // That's why we're checking if we'll be able to successfull instantiate + // \DateTimeZone() before doing so. Otherwise we could simply instantiate + // and catch the exception. + $tzIdentifiers = \DateTimeZone::listIdentifiers(); + + try { + if ( + (in_array($tzid, $tzIdentifiers)) || + (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) || + (in_array($tzid, self::getIdentifiersBC())) + ) { + return new \DateTimeZone($tzid); + } + } catch(\Exception $e) { + } + + } + + self::loadTzMaps(); + + // Next, we check if the tzid is somewhere in our tzid map. + if (isset(self::$map[$tzid])) { + return new \DateTimeZone(self::$map[$tzid]); + } + + // Maybe the author was hyper-lazy and just included an offset. We + // support it, but we aren't happy about it. + if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { + + // Note that the path in the source will never be taken from PHP 5.5.10 + // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it + // already gets returned early in this function. Once we drop support + // for versions under PHP 5.5.10, this bit can be taken out of the + // source. + // @codeCoverageIgnoreStart + return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0')); + // @codeCoverageIgnoreEnd + } + + if ($vcalendar) { + + // If that didn't work, we will scan VTIMEZONE objects + foreach($vcalendar->select('VTIMEZONE') as $vtimezone) { + + if ((string)$vtimezone->TZID === $tzid) { + + // Some clients add 'X-LIC-LOCATION' with the olson name. + if (isset($vtimezone->{'X-LIC-LOCATION'})) { + + $lic = (string)$vtimezone->{'X-LIC-LOCATION'}; + + // Libical generators may specify strings like + // "SystemV/EST5EDT". For those we must remove the + // SystemV part. + if (substr($lic,0,8)==='SystemV/') { + $lic = substr($lic,8); + } + + return self::getTimeZone($lic, null, $failIfUncertain); + + } + // Microsoft may add a magic number, which we also have an + // answer for. + if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { + $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); + + // 2 can mean both Europe/Lisbon and Europe/Sarajevo. + if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) { + return new \DateTimeZone('Europe/Sarajevo'); + } + + if (isset(self::$microsoftExchangeMap[$cdoId])) { + return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); + } + } + + } + + } + + } + + if ($failIfUncertain) { + throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); + } + + // If we got all the way here, we default to UTC. + return new \DateTimeZone(date_default_timezone_get()); + + } + + /** + * This method will load in all the tz mapping information, if it's not yet + * done. + */ + static public function loadTzMaps() { + + if (!is_null(self::$map)) return; + + self::$map = array_merge( + include __DIR__ . '/timezonedata/windowszones.php', + include __DIR__ . '/timezonedata/lotuszones.php', + include __DIR__ . '/timezonedata/exchangezones.php', + include __DIR__ . '/timezonedata/php-workaround.php' + ); + + } + + /** + * This method returns an array of timezone identifiers, that are supported + * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers() + * + * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: + * - It's not supported by some PHP versions as well as HHVM. + * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. + * (See timezonedata/php-bc.php and timezonedata php-workaround.php) + * + * @return array + */ + static public function getIdentifiersBC() { + return include __DIR__ . '/timezonedata/php-bc.php'; + } + +} diff --git a/vendor/sabre/vobject/lib/UUIDUtil.php b/vendor/sabre/vobject/lib/UUIDUtil.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/UUIDUtil.php @@ -0,0 +1,67 @@ +getDocumentType(); + if ($inputVersion===$targetVersion) { + return clone $input; + } + + if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) { + throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); + } + if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) { + throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); + } + + $newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0'; + + $output = new Component\VCard(array( + 'VERSION' => $newVersion, + )); + + foreach($input->children as $property) { + + $this->convertProperty($input, $output, $property, $targetVersion); + + } + + return $output; + + } + + /** + * Handles conversion of a single property. + * + * @param Component\VCard $input + * @param Component\VCard $output + * @param Property $property + * @param int $targetVersion + * @return void + */ + protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) { + + // Skipping these, those are automatically added. + if (in_array($property->name, array('VERSION', 'PRODID'))) { + return; + } + + $parameters = $property->parameters(); + $valueType = null; + if (isset($parameters['VALUE'])) { + $valueType = $parameters['VALUE']->getValue(); + unset($parameters['VALUE']); + } + if (!$valueType) { + $valueType = $property->getValueType(); + } + $newProperty = $output->createProperty( + $property->name, + $property->getParts(), + array(), // parameters will get added a bit later. + $valueType + ); + + + if ($targetVersion===Document::VCARD30) { + + if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) { + + $newProperty = $this->convertUriToBinary($output, $newProperty); + + } elseif ($property instanceof Property\VCard\DateAndOrTime) { + + // In vCard 4, the birth year may be optional. This is not the + // case for vCard 3. Apple has a workaround for this that + // allows applications that support Apple's extension still + // omit birthyears in vCard 3, but applications that do not + // support this, will just use a random birthyear. We're + // choosing 1604 for the birthyear, because that's what apple + // uses. + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); + if (is_null($parts['year'])) { + $newValue = '1604-' . $parts['month'] . '-' . $parts['date']; + $newProperty->setValue($newValue); + $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; + } + + if ($newProperty->name == 'ANNIVERSARY') { + // Microsoft non-standard anniversary + $newProperty->name = 'X-ANNIVERSARY'; + + // We also need to add a new apple property for the same + // purpose. This apple property needs a 'label' in the same + // group, so we first need to find a groupname that doesn't + // exist yet. + $x = 1; + while($output->select('ITEM' . $x . '.')) { + $x++; + } + $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), array('VALUE' => 'DATE-AND-OR-TIME')); + $output->add('ITEM' . $x . '.X-ABLABEL', '_$!!$_'); + } + + } elseif ($property->name === 'KIND') { + + switch(strtolower($property->getValue())) { + case 'org' : + // vCard 3.0 does not have an equivalent to KIND:ORG, + // but apple has an extension that means the same + // thing. + $newProperty = $output->createProperty('X-ABSHOWAS','COMPANY'); + break; + + case 'individual' : + // Individual is implicit, so we skip it. + return; + + case 'group' : + // OS X addressbook property + $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP'); + break; + } + + + } + + } elseif ($targetVersion===Document::VCARD40) { + + // These properties were removed in vCard 4.0 + if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) { + return; + } + + if ($property instanceof Property\Binary) { + + $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); + + } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { + + // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', + // then we're stripping the year from the vcard 4 value. + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); + if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) { + $newValue = '--' . $parts['month'] . '-' . $parts['date']; + $newProperty->setValue($newValue); + } + + // Regardless if the year matched or not, we do need to strip + // X-APPLE-OMIT-YEAR. + unset($parameters['X-APPLE-OMIT-YEAR']); + + } + switch($property->name) { + case 'X-ABSHOWAS' : + if (strtoupper($property->getValue()) === 'COMPANY') { + $newProperty = $output->createProperty('KIND','ORG'); + } + break; + case 'X-ADDRESSBOOKSERVER-KIND' : + if (strtoupper($property->getValue()) === 'GROUP') { + $newProperty = $output->createProperty('KIND','GROUP'); + } + break; + case 'X-ANNIVERSARY' : + $newProperty->name = 'ANNIVERSARY'; + // If we already have an anniversary property with the same + // value, ignore. + foreach ($output->select('ANNIVERSARY') as $anniversary) { + if ($anniversary->getValue() === $newProperty->getValue()) { + return; + } + } + break; + case 'X-ABDATE' : + // Find out what the label was, if it exists. + if (!$property->group) { + break; + } + $label = $input->{$property->group . '.X-ABLABEL'}; + + // We only support converting anniversaries. + if (!$label || $label->getValue()!=='_$!!$_') { + break; + } + + // If we already have an anniversary property with the same + // value, ignore. + foreach ($output->select('ANNIVERSARY') as $anniversary) { + if ($anniversary->getValue() === $newProperty->getValue()) { + return; + } + } + $newProperty->name = 'ANNIVERSARY'; + break; + // Apple's per-property label system. + case 'X-ABLABEL' : + if($newProperty->getValue() === '_$!!$_') { + // We can safely remove these, as they are converted to + // ANNIVERSARY properties. + return; + } + break; + + } + + } + + // set property group + $newProperty->group = $property->group; + + if ($targetVersion===Document::VCARD40) { + $this->convertParameters40($newProperty, $parameters); + } else { + $this->convertParameters30($newProperty, $parameters); + } + + // Lastly, we need to see if there's a need for a VALUE parameter. + // + // We can do that by instantating a empty property with that name, and + // seeing if the default valueType is identical to the current one. + $tempProperty = $output->createProperty($newProperty->name); + if ($tempProperty->getValueType() !== $newProperty->getValueType()) { + $newProperty['VALUE'] = $newProperty->getValueType(); + } + + $output->add($newProperty); + + + } + + /** + * Converts a BINARY property to a URI property. + * + * vCard 4.0 no longer supports BINARY properties. + * + * @param Component\VCard $output + * @param Property\Uri $property The input property. + * @param $parameters List of parameters that will eventually be added to + * the new property. + * @return Property\Uri + */ + protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) { + + $value = $newProperty->getValue(); + $newProperty = $output->createProperty( + $newProperty->name, + null, // no value + array(), // no parameters yet + 'URI' // Forcing the BINARY type + ); + + $mimeType = 'application/octet-stream'; + + // See if we can find a better mimetype. + if (isset($parameters['TYPE'])) { + + $newTypes = array(); + foreach($parameters['TYPE']->getParts() as $typePart) { + if (in_array( + strtoupper($typePart), + array('JPEG','PNG','GIF') + )) { + $mimeType = 'image/' . strtolower($typePart); + } else { + $newTypes[] = $typePart; + } + } + + // If there were any parameters we're not converting to a + // mime-type, we need to keep them. + if ($newTypes) { + $parameters['TYPE']->setParts($newTypes); + } else { + unset($parameters['TYPE']); + } + + } + + $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value)); + return $newProperty; + + } + + /** + * Converts a URI property to a BINARY property. + * + * In vCard 4.0 attachments are encoded as data: uri. Even though these may + * be valid in vCard 3.0 as well, we should convert those to BINARY if + * possible, to improve compatibility. + * + * @param Component\VCard $output + * @param Property\Uri $property The input property. + * @return Property\Binary|null + */ + protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) { + + $value = $newProperty->getValue(); + + // Only converting data: uris + if (substr($value, 0, 5)!=='data:') { + return $newProperty; + } + + $newProperty = $output->createProperty( + $newProperty->name, + null, // no value + array(), // no parameters yet + 'BINARY' + ); + + $mimeType = substr($value, 5, strpos($value, ',')-5); + if (strpos($mimeType, ';')) { + $mimeType = substr($mimeType,0,strpos($mimeType, ';')); + $newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1))); + } else { + $newProperty->setValue(substr($value, strpos($value,',')+1)); + } + unset($value); + + $newProperty['ENCODING'] = 'b'; + switch($mimeType) { + + case 'image/jpeg' : + $newProperty['TYPE'] = 'JPEG'; + break; + case 'image/png' : + $newProperty['TYPE'] = 'PNG'; + break; + case 'image/gif' : + $newProperty['TYPE'] = 'GIF'; + break; + + } + + + return $newProperty; + + } + + /** + * Adds parameters to a new property for vCard 4.0 + * + * @param Property $newProperty + * @param array $parameters + * @return void + */ + protected function convertParameters40(Property $newProperty, array $parameters) { + + // Adding all parameters. + foreach($parameters as $param) { + + // vCard 2.1 allowed parameters with no name + if ($param->noName) $param->noName = false; + + switch($param->name) { + + // We need to see if there's any TYPE=PREF, because in vCard 4 + // that's now PREF=1. + case 'TYPE' : + foreach($param->getParts() as $paramPart) { + + if (strtoupper($paramPart)==='PREF') { + $newProperty->add('PREF','1'); + } else { + $newProperty->add($param->name, $paramPart); + } + + } + break; + // These no longer exist in vCard 4 + case 'ENCODING' : + case 'CHARSET' : + break; + + default : + $newProperty->add($param->name, $param->getParts()); + break; + + } + + } + + } + + /** + * Adds parameters to a new property for vCard 3.0 + * + * @param Property $newProperty + * @param array $parameters + * @return void + */ + protected function convertParameters30(Property $newProperty, array $parameters) { + + // Adding all parameters. + foreach($parameters as $param) { + + // vCard 2.1 allowed parameters with no name + if ($param->noName) $param->noName = false; + + switch($param->name) { + + case 'ENCODING' : + // This value only existed in vCard 2.1, and should be + // removed for anything else. + if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') { + $newProperty->add($param->name, $param->getParts()); + } + break; + + /* + * Converting PREF=1 to TYPE=PREF. + * + * Any other PREF numbers we'll drop. + */ + case 'PREF' : + if ($param->getValue()=='1') { + $newProperty->add('TYPE','PREF'); + } + break; + + default : + $newProperty->add($param->name, $param->getParts()); + break; + + } + + } + + } +} diff --git a/vendor/sabre/vobject/lib/Version.php b/vendor/sabre/vobject/lib/Version.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/Version.php @@ -0,0 +1,19 @@ + 'UTC', + 'Casablanca, Monrovia' => 'Africa/Casablanca', + 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon', + 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London', + 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', + 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague', + 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', + 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris', + 'Prague, Central Europe' => 'Europe/Prague', + 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo', + 'West Central Africa' => 'Africa/Luanda', // This was a best guess + 'Athens, Istanbul, Minsk' => 'Europe/Athens', + 'Bucharest' => 'Europe/Bucharest', + 'Cairo' => 'Africa/Cairo', + 'Harare, Pretoria' => 'Africa/Harare', + 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki', + 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem', + 'Baghdad' => 'Asia/Baghdad', + 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait', + 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', + 'East Africa, Nairobi' => 'Africa/Nairobi', + 'Tehran' => 'Asia/Tehran', + 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess + 'Baku, Tbilisi, Yerevan' => 'Asia/Baku', + 'Kabul' => 'Asia/Kabul', + 'Ekaterinburg' => 'Asia/Yekaterinburg', + 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi', + 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta', + 'Kathmandu, Nepal' => 'Asia/Kathmandu', + 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty', + 'Astana, Dhaka' => 'Asia/Dhaka', + 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo', + 'Rangoon' => 'Asia/Rangoon', + 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', + 'Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai', + 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk', + 'Kuala Lumpur, Singapore' => 'Asia/Singapore', + 'Perth, Western Australia' => 'Australia/Perth', + 'Taipei' => 'Asia/Taipei', + 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', + 'Seoul, Korea Standard time' => 'Asia/Seoul', + 'Yakutsk' => 'Asia/Yakutsk', + 'Adelaide, Central Australia' => 'Australia/Adelaide', + 'Darwin' => 'Australia/Darwin', + 'Brisbane, East Australia' => 'Australia/Brisbane', + 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney', + 'Guam, Port Moresby' => 'Pacific/Guam', + 'Hobart, Tasmania' => 'Australia/Hobart', + 'Vladivostok' => 'Asia/Vladivostok', + 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan', + 'Auckland, Wellington' => 'Pacific/Auckland', + 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji', + 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu', + 'Azores' => 'Atlantic/Azores', + 'Cape Verde Is.' => 'Atlantic/Cape_Verde', + 'Mid-Atlantic' => 'America/Noronha', + 'Brasilia' => 'America/Sao_Paulo', // Best guess + 'Buenos Aires' => 'America/Argentina/Buenos_Aires', + 'Greenland' => 'America/Godthab', + 'Newfoundland' => 'America/St_Johns', + 'Atlantic Time (Canada)' => 'America/Halifax', + 'Caracas, La Paz' => 'America/Caracas', + 'Santiago' => 'America/Santiago', + 'Bogota, Lima, Quito' => 'America/Bogota', + 'Eastern Time (US & Canada)' => 'America/New_York', + 'Indiana (East)' => 'America/Indiana/Indianapolis', + 'Central America' => 'America/Guatemala', + 'Central Time (US & Canada)' => 'America/Chicago', + 'Mexico City, Tegucigalpa' => 'America/Mexico_City', + 'Saskatchewan' => 'America/Edmonton', + 'Arizona' => 'America/Phoenix', + 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess + 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess + 'Alaska' => 'America/Anchorage', + 'Hawaii' => 'Pacific/Honolulu', + 'Midway Island, Samoa' => 'Pacific/Midway', + 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', +); diff --git a/vendor/sabre/vobject/lib/timezonedata/lotuszones.php b/vendor/sabre/vobject/lib/timezonedata/lotuszones.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/timezonedata/lotuszones.php @@ -0,0 +1,101 @@ + 'Etc/GMT-12', + 'Samoa' => 'Pacific/Apia', + 'Hawaiian' => 'Pacific/Honolulu', + 'Alaskan' => 'America/Anchorage', + 'Pacific' => 'America/Los_Angeles', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Mexico Standard Time 2' => 'America/Chihuahua', + 'Mountain' => 'America/Denver', + // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones. + 'US Mountain' => 'America/Phoenix', + 'Canada Central' => 'America/Edmonton', + 'Central America' => 'America/Guatemala', + 'Central' => 'America/Chicago', + // 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones. + 'Mexico' => 'America/Mexico_City', + 'Eastern' => 'America/New_York', + 'SA Pacific' => 'America/Bogota', + 'US Eastern' => 'America/Indiana/Indianapolis', + 'Venezuela' => 'America/Caracas', + 'Atlantic' => 'America/Halifax', + 'Central Brazilian' => 'America/Manaus', + 'Pacific SA' => 'America/Santiago', + 'SA Western' => 'America/La_Paz', + 'Newfoundland' => 'America/St_Johns', + 'Argentina' => 'America/Argentina/Buenos_Aires', + 'E. South America' => 'America/Belem', + 'Greenland' => 'America/Godthab', + 'Montevideo' => 'America/Montevideo', + 'SA Eastern' => 'America/Belem', + // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones. + 'Azores' => 'Atlantic/Azores', + 'Cape Verde' => 'Atlantic/Cape_Verde', + 'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT. + 'Morocco' => 'Africa/Casablanca', + 'Central Europe' => 'Europe/Prague', + 'Central European' => 'Europe/Sarajevo', + 'Romance' => 'Europe/Paris', + 'W. Central Africa' => 'Africa/Lagos', // Best guess + 'W. Europe' => 'Europe/Amsterdam', + 'E. Europe' => 'Europe/Minsk', + 'Egypt' => 'Africa/Cairo', + 'FLE' => 'Europe/Helsinki', + 'GTB' => 'Europe/Athens', + 'Israel' => 'Asia/Jerusalem', + 'Jordan' => 'Asia/Amman', + 'Middle East' => 'Asia/Beirut', + 'Namibia' => 'Africa/Windhoek', + 'South Africa' => 'Africa/Harare', + 'Arab' => 'Asia/Kuwait', + 'Arabic' => 'Asia/Baghdad', + 'E. Africa' => 'Africa/Nairobi', + 'Georgian' => 'Asia/Tbilisi', + 'Russian' => 'Europe/Moscow', + 'Iran' => 'Asia/Tehran', + 'Arabian' => 'Asia/Muscat', + 'Armenian' => 'Asia/Yerevan', + 'Azerbijan' => 'Asia/Baku', + 'Caucasus' => 'Asia/Yerevan', + 'Mauritius' => 'Indian/Mauritius', + 'Afghanistan' => 'Asia/Kabul', + 'Ekaterinburg' => 'Asia/Yekaterinburg', + 'Pakistan' => 'Asia/Karachi', + 'West Asia' => 'Asia/Tashkent', + 'India' => 'Asia/Calcutta', + 'Sri Lanka' => 'Asia/Colombo', + 'Nepal' => 'Asia/Kathmandu', + 'Central Asia' => 'Asia/Dhaka', + 'N. Central Asia' => 'Asia/Almaty', + 'Myanmar' => 'Asia/Rangoon', + 'North Asia' => 'Asia/Krasnoyarsk', + 'SE Asia' => 'Asia/Bangkok', + 'China' => 'Asia/Shanghai', + 'North Asia East' => 'Asia/Irkutsk', + 'Singapore' => 'Asia/Singapore', + 'Taipei' => 'Asia/Taipei', + 'W. Australia' => 'Australia/Perth', + 'Korea' => 'Asia/Seoul', + 'Tokyo' => 'Asia/Tokyo', + 'Yakutsk' => 'Asia/Yakutsk', + 'AUS Central' => 'Australia/Darwin', + 'Cen. Australia' => 'Australia/Adelaide', + 'AUS Eastern' => 'Australia/Sydney', + 'E. Australia' => 'Australia/Brisbane', + 'Tasmania' => 'Australia/Hobart', + 'Vladivostok' => 'Asia/Vladivostok', + 'West Pacific' => 'Pacific/Guam', + 'Central Pacific' => 'Asia/Magadan', + 'Fiji' => 'Pacific/Fiji', + 'New Zealand' => 'Pacific/Auckland', + 'Tonga' => 'Pacific/Tongatapu', +); diff --git a/vendor/sabre/vobject/lib/timezonedata/php-bc.php b/vendor/sabre/vobject/lib/timezonedata/php-bc.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/timezonedata/php-bc.php @@ -0,0 +1,153 @@ + 'America/Chicago', + 'Cuba' => 'America/Havana', + 'Egypt' => 'Africa/Cairo', + 'Eire' => 'Europe/Dublin', + 'EST5EDT' => 'America/New_York', + 'Factory' => 'UTC', + 'GB-Eire' => 'Europe/London', + 'GMT0' => 'UTC', + 'Greenwich' => 'UTC', + 'Hongkong' => 'Asia/Hong_Kong', + 'Iceland' => 'Atlantic/Reykjavik', + 'Iran' => 'Asia/Tehran', + 'Israel' => 'Asia/Jerusalem', + 'Jamaica' => 'America/Jamaica', + 'Japan' => 'Asia/Tokyo', + 'Kwajalein' => 'Pacific/Kwajalein', + 'Libya' => 'Africa/Tripoli', + 'MST7MDT' => 'America/Denver', + 'Navajo' => 'America/Denver', + 'NZ-CHAT' => 'Pacific/Chatham', + 'Poland' => 'Europe/Warsaw', + 'Portugal' => 'Europe/Lisbon', + 'PST8PDT' => 'America/Los_Angeles', + 'Singapore' => 'Asia/Singapore', + 'Turkey' => 'Europe/Istanbul', + 'Universal' => 'UTC', + 'W-SU' => 'Europe/Moscow', + 'Zulu' => 'UTC', +); diff --git a/vendor/sabre/vobject/lib/timezonedata/windowszones.php b/vendor/sabre/vobject/lib/timezonedata/windowszones.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/lib/timezonedata/windowszones.php @@ -0,0 +1,118 @@ + 'Australia/Darwin', + 'AUS Eastern Standard Time' => 'Australia/Sydney', + 'Afghanistan Standard Time' => 'Asia/Kabul', + 'Alaskan Standard Time' => 'America/Anchorage', + 'Arab Standard Time' => 'Asia/Riyadh', + 'Arabian Standard Time' => 'Asia/Dubai', + 'Arabic Standard Time' => 'Asia/Baghdad', + 'Argentina Standard Time' => 'America/Buenos_Aires', + 'Atlantic Standard Time' => 'America/Halifax', + 'Azerbaijan Standard Time' => 'Asia/Baku', + 'Azores Standard Time' => 'Atlantic/Azores', + 'Bahia Standard Time' => 'America/Bahia', + 'Bangladesh Standard Time' => 'Asia/Dhaka', + 'Belarus Standard Time' => 'Europe/Minsk', + 'Canada Central Standard Time' => 'America/Regina', + 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', + 'Caucasus Standard Time' => 'Asia/Yerevan', + 'Cen. Australia Standard Time' => 'Australia/Adelaide', + 'Central America Standard Time' => 'America/Guatemala', + 'Central Asia Standard Time' => 'Asia/Almaty', + 'Central Brazilian Standard Time' => 'America/Cuiaba', + 'Central Europe Standard Time' => 'Europe/Budapest', + 'Central European Standard Time' => 'Europe/Warsaw', + 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', + 'Central Standard Time' => 'America/Chicago', + 'Central Standard Time (Mexico)' => 'America/Mexico_City', + 'China Standard Time' => 'Asia/Shanghai', + 'Dateline Standard Time' => 'Etc/GMT+12', + 'E. Africa Standard Time' => 'Africa/Nairobi', + 'E. Australia Standard Time' => 'Australia/Brisbane', + 'E. South America Standard Time' => 'America/Sao_Paulo', + 'Eastern Standard Time' => 'America/New_York', + 'Egypt Standard Time' => 'Africa/Cairo', + 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', + 'FLE Standard Time' => 'Europe/Kiev', + 'Fiji Standard Time' => 'Pacific/Fiji', + 'GMT Standard Time' => 'Europe/London', + 'GTB Standard Time' => 'Europe/Bucharest', + 'Georgian Standard Time' => 'Asia/Tbilisi', + 'Greenland Standard Time' => 'America/Godthab', + 'Greenwich Standard Time' => 'Atlantic/Reykjavik', + 'Hawaiian Standard Time' => 'Pacific/Honolulu', + 'India Standard Time' => 'Asia/Calcutta', + 'Iran Standard Time' => 'Asia/Tehran', + 'Israel Standard Time' => 'Asia/Jerusalem', + 'Jordan Standard Time' => 'Asia/Amman', + 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', + 'Korea Standard Time' => 'Asia/Seoul', + 'Libya Standard Time' => 'Africa/Tripoli', + 'Line Islands Standard Time' => 'Pacific/Kiritimati', + 'Magadan Standard Time' => 'Asia/Magadan', + 'Mauritius Standard Time' => 'Indian/Mauritius', + 'Middle East Standard Time' => 'Asia/Beirut', + 'Montevideo Standard Time' => 'America/Montevideo', + 'Morocco Standard Time' => 'Africa/Casablanca', + 'Mountain Standard Time' => 'America/Denver', + 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', + 'Myanmar Standard Time' => 'Asia/Rangoon', + 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', + 'Namibia Standard Time' => 'Africa/Windhoek', + 'Nepal Standard Time' => 'Asia/Katmandu', + 'New Zealand Standard Time' => 'Pacific/Auckland', + 'Newfoundland Standard Time' => 'America/St_Johns', + 'North Asia East Standard Time' => 'Asia/Irkutsk', + 'North Asia Standard Time' => 'Asia/Krasnoyarsk', + 'Pacific SA Standard Time' => 'America/Santiago', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel', + 'Pakistan Standard Time' => 'Asia/Karachi', + 'Paraguay Standard Time' => 'America/Asuncion', + 'Romance Standard Time' => 'Europe/Paris', + 'Russia Time Zone 10' => 'Asia/Srednekolymsk', + 'Russia Time Zone 11' => 'Asia/Kamchatka', + 'Russia Time Zone 3' => 'Europe/Samara', + 'Russian Standard Time' => 'Europe/Moscow', + 'SA Eastern Standard Time' => 'America/Cayenne', + 'SA Pacific Standard Time' => 'America/Bogota', + 'SA Western Standard Time' => 'America/La_Paz', + 'SE Asia Standard Time' => 'Asia/Bangkok', + 'Samoa Standard Time' => 'Pacific/Apia', + 'Singapore Standard Time' => 'Asia/Singapore', + 'South Africa Standard Time' => 'Africa/Johannesburg', + 'Sri Lanka Standard Time' => 'Asia/Colombo', + 'Syria Standard Time' => 'Asia/Damascus', + 'Taipei Standard Time' => 'Asia/Taipei', + 'Tasmania Standard Time' => 'Australia/Hobart', + 'Tokyo Standard Time' => 'Asia/Tokyo', + 'Tonga Standard Time' => 'Pacific/Tongatapu', + 'Turkey Standard Time' => 'Europe/Istanbul', + 'US Eastern Standard Time' => 'America/Indianapolis', + 'US Mountain Standard Time' => 'America/Phoenix', + 'UTC' => 'Etc/GMT', + 'UTC+12' => 'Etc/GMT-12', + 'UTC-02' => 'Etc/GMT+2', + 'UTC-11' => 'Etc/GMT+11', + 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', + 'Venezuela Standard Time' => 'America/Caracas', + 'Vladivostok Standard Time' => 'Asia/Vladivostok', + 'W. Australia Standard Time' => 'Australia/Perth', + 'W. Central Africa Standard Time' => 'Africa/Lagos', + 'W. Europe Standard Time' => 'Europe/Berlin', + 'West Asia Standard Time' => 'Asia/Tashkent', + 'West Pacific Standard Time' => 'Pacific/Port_Moresby', + 'Yakutsk Standard Time' => 'Asia/Yakutsk', +); diff --git a/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php b/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php @@ -0,0 +1,22 @@ +assertEquals($event, $obj->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/CliTest.php b/vendor/sabre/vobject/tests/VObject/CliTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/CliTest.php @@ -0,0 +1,650 @@ +cli = new CliMock(); + $this->cli->stderr = fopen('php://memory','r+'); + $this->cli->stdout = fopen('php://memory','r+'); + + } + + public function testInvalidArg() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--hi')) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testQuiet() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '-q')) + ); + $this->assertTrue($this->cli->quiet); + + rewind($this->cli->stderr); + $this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr))); + + } + + public function testHelp() { + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', '-h')) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testFormat() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--format=jcard')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertEquals('jcard', $this->cli->format); + + } + + public function testFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--format=foo')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + public function testInputFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--inputformat=foo')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + + public function testNoInputFile() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'color')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testTooManyArgs() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'color', 'a', 'b', 'c')) + ); + + } + + public function testUnknownCommand() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'foo', '-')) + ); + + } + + public function testConvertJson() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=json', '-')) + ); + + rewind($this->cli->stdout); + $version = Version::VERSION; + $this->assertEquals( + '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject '. $version .'\/\/EN"],["fn",{},"text","Cowboy Henk"]]]', + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertJCardPretty() { + + if (version_compare(PHP_VERSION, '5.4.0') < 0) { + $this->markTestSkipped('This test required PHP 5.4.0'); + } + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=jcard', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + $version = Version::VERSION; + + // PHP 5.5.12 changed the output + + $expected = <<assertStringStartsWith( + $expected, + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertJCalFail() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','--format=jcal', '--inputformat=mimedir', '-')) + ); + + } + + public function testConvertMimeDir() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + $expected = <<assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertDefaultFormats() { + + $inputStream = fopen('php://memory','r+'); + $outputFile = SABRE_TEMPDIR . 'bar.json'; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','foo.json',$outputFile)) + ); + + $this->assertEquals('json', $this->cli->inputFormat); + $this->assertEquals('json', $this->cli->format); + + } + + public function testConvertDefaultFormats2() { + + $outputFile = SABRE_TEMPDIR . 'bar.ics'; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','foo.ics',$outputFile)) + ); + + $this->assertEquals('mimedir', $this->cli->inputFormat); + $this->assertEquals('mimedir', $this->cli->format); + + } + + public function testVCard3040() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + + $version = Version::VERSION; + $expected = <<assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testVCard4030() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-')) + ); + + $version = Version::VERSION; + + rewind($this->cli->stdout); + $expected = <<assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testVCard4021() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','--format=vcard21', '--pretty', '-')) + ); + + } + + function testValidate() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + $result = $this->cli->main(array('vobject', 'validate', '-')); + + $this->assertEquals( + 0, + $result + ); + + } + + function testValidateFail() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'validate', '-')) + ); + + } + + function testValidateFail2() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'validate', '-')) + ); + + } + + function testRepair() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'repair', '-')) + ); + + rewind($this->cli->stdout); + $this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout)); + } + + function testRepairNothing() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'repair', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorCalendar() { + + $inputStream = fopen('php://memory','r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'color', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorVCard() { + + $inputStream = fopen('php://memory','r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'color', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } +} + +class CliMock extends Cli { + + public $log = array(); + + public $quiet = false; + + public $format; + + public $pretty; + + public $stdin; + + public $stdout; + + public $stderr; + + public $inputFormat; + + public $outputFormat; + +} diff --git a/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php b/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php @@ -0,0 +1,179 @@ +assertEquals($outcome, $valarm->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + // Hard date and time + $valarm1 = $calendar->createComponent('VALARM'); + $valarm1->add( + $calendar->createProperty('TRIGGER', '20120312T130000Z', array('VALUE' => 'DATE-TIME')) + ); + + $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + // Relation to start time of event + $valarm2 = $calendar->createComponent('VALARM'); + $valarm2->add( + $calendar->createProperty('TRIGGER', '-P1D', array('VALUE' => 'DURATION')) + ); + + $vevent2 = $calendar->createComponent('VEVENT'); + $vevent2->DTSTART = '20120313T130000Z'; + $vevent2->add($valarm2); + + $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + // Relation to end time of event + $valarm3 = $calendar->createComponent('VALARM'); + $valarm3->add( $calendar->createProperty('TRIGGER', '-P1D', array('VALUE'=>'DURATION', 'RELATED' => 'END')) ); + + $vevent3 = $calendar->createComponent('VEVENT'); + $vevent3->DTSTART = '20120301T130000Z'; + $vevent3->DTEND = '20120401T130000Z'; + $vevent3->add($valarm3); + + $tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to end time of todo + $valarm4 = $calendar->createComponent('VALARM'); + $valarm4->TRIGGER = '-P1D'; + $valarm4->TRIGGER['VALUE'] = 'DURATION'; + $valarm4->TRIGGER['RELATED']= 'END'; + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DTSTART = '20120301T130000Z'; + $vtodo4->DUE = '20120401T130000Z'; + $vtodo4->add($valarm4); + + $tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to start time of event + repeat + $valarm5 = $calendar->createComponent('VALARM'); + $valarm5->TRIGGER = '-P1D'; + $valarm5->TRIGGER['VALUE'] = 'DURATION'; + $valarm5->REPEAT = 10; + $valarm5->DURATION = 'P1D'; + + $vevent5 = $calendar->createComponent('VEVENT'); + $vevent5->DTSTART = '20120301T130000Z'; + $vevent5->add($valarm5); + + $tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true); + + // Relation to start time of event + duration, but no repeat + $valarm6 = $calendar->createComponent('VALARM'); + $valarm6->TRIGGER = '-P1D'; + $valarm6->TRIGGER['VALUE'] = 'DURATION'; + $valarm6->DURATION = 'P1D'; + + $vevent6 = $calendar->createComponent('VEVENT'); + $vevent6->DTSTART = '20120313T130000Z'; + $vevent6->add($valarm6); + + $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + + // Relation to end time of event (DURATION instead of DTEND) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED']= 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->DURATION = 'P30D'; + $vevent7->add($valarm7); + + $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to end time of event (No DTEND or DURATION) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED']= 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->add($valarm7); + + $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true); + $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false); + + + return $tests; + } + + /** + * @expectedException LogicException + */ + public function testInTimeRangeInvalidComponent() { + + $calendar = new VCalendar(); + $valarm = $calendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P1D'; + $valarm->TRIGGER['RELATED'] = 'END'; + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->add($valarm); + + $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00')); + + } + + /** + * This bug was found and reported on the mailing list. + */ + public function testInTimeRangeBuggy() { + +$input = <<assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00'))); + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php b/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php @@ -0,0 +1,385 @@ +assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY); + + } + + function testRFCxxxSection3_1_availabilityprop_required() { + + // UID and DTSTAMP are present. + $this->assertIsValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsValid(Reader::read($this->template($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->template(array( + $property, + $property + )))); + } + + } + + function testRFCxxxSection3_1_availabilityprop_dtend_duration() { + + // Only DTEND. + $this->assertIsValid(Reader::read($this->template(array( + 'DTEND:21111005T133225Z' + )))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->template(array( + 'DURATION:PT1H' + )))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->template(array( + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + )))); + } + + function testAvailableSubComponent() { + + $vcal = <<assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE); + + } + + function testRFCxxxSection3_1_availableprop_required() { + + // UID, DTSTAMP and DTSTART are present. + $this->assertIsValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsNotValid(Reader::read( +<<assertIsValid(Reader::read($this->templateAvailable(array( + 'DTEND:21111005T133225Z' + )))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->templateAvailable(array( + 'DURATION:PT1H' + )))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->templateAvailable(array( + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + )))); + } + + function testRFCxxxSection3_1_available_optional_once() { + + $properties = array( + 'CREATED:20111005T135125Z', + 'DESCRIPTION:Long bla bla', + 'LAST-MODIFIED:20111005T135325Z', + 'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z', + 'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', + 'SUMMARY:Bla bla' + ); + + // They are all present, only once. + $this->assertIsValid(Reader::read($this->templateAvailable($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->templateAvailable(array( + $property, + $property + )))); + } + + } + function testRFCxxxSection3_2() { + + $this->assertEquals( + 'BUSY', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-UNAVAILABLE', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY-UNAVAILABLE' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-TENTATIVE', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY-TENTATIVE' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + } + + protected function assertIsValid(VObject\Document $document) { + + $this->assertEmpty($document->validate()); + + } + + protected function assertIsNotValid(VObject\Document $document) { + + $this->assertNotEmpty($document->validate()); + + } + + protected function template(array $properties) { + + return $this->_template( + <<_template( + <<expand( + new \DateTime($start), + new \DateTime($end), + $timeZone + ); + + // This will normalize the output + $output = VObject\Reader::read($output)->serialize(); + + $this->assertEquals($output, $vcal->serialize()); + + } + + public function expandData() { + + $tests = array(); + + // No data + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +END:VCALENDAR +'; + + $output = $input; + $tests[] = array($input,$output); + + + // Simple events + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +BEGIN:VEVENT +UID:bla2 +SUMMARY:NotInExpand +DTSTART;VALUE=DATE:20120101 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Removing timezone info + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +END:VTIMEZONE +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART;TZID=Europe/Paris:20111203T130102 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART:20111203T120102Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Recurrence rule + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111209T120000Z +DTEND:20111209T130000Z +RECURRENCE-ID:20111209T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Recurrence rule + override + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Floating dates and times. + $input = <<expand( + new \DateTime('2011-12-01'), + new \DateTime('2011-12-31') + ); + + } + + function testGetDocumentType() { + + $vcard = new VCalendar(); + $vcard->VERSION = '2.0'; + $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType()); + + } + + function testValidateCorrect() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +DTSTAMP:20140122T233226Z +UID:foo +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(array(), $vcal->validate(), 'Got an error'); + + } + + function testValidateNoVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateWrongVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:3.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateNoProdId() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleCalScale() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +CALSCALE:GREGORIAN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleMethod() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateTwoMasterEvents() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateOneMasterEvent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(0, count($vcal->validate())); + + } + + function testGetBaseComponent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertEquals('test', $result->SUMMARY->getValue()); + + } + + function testGetBaseComponentNoResult() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +RECURRENCE-ID;VALUE=DATE:20111202 +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertNull($result); + + } + + function testNoComponents() { + + $input = <<assertValidate( + $input, + 0, + 3, + "An iCalendar object must have at least 1 component." + ); + + } + + function testCalDAVNoComponents() { + + $input = <<assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)." + ); + + } + + function testCalDAVMultiUID() { + + $input = <<assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have components with the same UID." + ); + + } + + function testCalDAVMultiComponent() { + + $input = <<assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)." + ); + + } + + function testCalDAVMETHOD() { + + $input = <<assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server MUST NOT have a METHOD property." + ); + + } + + function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($ics); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = array(); + foreach($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } + + +} diff --git a/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php b/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php @@ -0,0 +1,288 @@ +validate(); + + $warnMsg = array(); + foreach($warnings as $warning) { + $warnMsg[] = $warning['message']; + } + + $this->assertEquals($expectedWarnings, $warnMsg); + + $vcard->validate(VObject\Component::REPAIR); + + $this->assertEquals( + $expectedRepairedOutput, + $vcard->serialize() + ); + + } + + public function validateData() { + + $tests = array(); + + // Correct + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array(), + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // No VERSION + $tests[] = array( + "BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'VERSION MUST appear exactly once in a VCARD component', + ), + "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // Unknown version + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + ), + "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // No FN + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + ); + // No FN, N fallback + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n", + ); + // No FN, N fallback, no first name + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n", + ); + + // No FN, ORG fallback + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n", + ); + return $tests; + + } + + function testGetDocumentType() { + + $vcard = new VCard(array(), false); + $vcard->VERSION = '2.1'; + $this->assertEquals(VCard::VCARD21, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $vcard->VERSION = '3.0'; + $this->assertEquals(VCard::VCARD30, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $vcard->VERSION = '4.0'; + $this->assertEquals(VCard::VCARD40, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType()); + } + + function testPreferredNoPref() { + + $vcard = <<assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWithPref() { + + $vcard = <<assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWith40Pref() { + + $vcard = <<assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredNotFound() { + + $vcard = <<assertNull($vcard->preferred('EMAIL')); + + } + + function testNoUIDCardDAV() { + + $vcard = <<assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'vCards on CardDAV servers MUST have a UID property.' + ); + + } + + function testNoUIDNoCardDAV() { + + $vcard = <<assertValidate( + $vcard, + 0, + 2, + 'Adding a UID to a vCard property is recommended.' + ); + + } + function testNoUIDNoCardDAVRepair() { + + $vcard = <<assertValidate( + $vcard, + VCARD::REPAIR, + 1, + 'Adding a UID to a vCard property is recommended.' + ); + + } + + function testVCard21CardDAV() { + + $vcard = <<assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'CardDAV servers are not allowed to accept vCard 2.1.' + ); + + } + + function testVCard21NoCardDAV() { + + $vcard = <<assertValidate( + $vcard, + 0, + 0 + ); + + } + + function assertValidate($vcf, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($vcf); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = array(); + foreach($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php b/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php @@ -0,0 +1,90 @@ +assertEquals($outcome, $vevent->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + $vevent = $calendar->createComponent('VEVENT'); + $vevent->DTSTART = '20111223T120000Z'; + $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent2 = clone $vevent; + $vevent2->DTEND = '20111225T120000Z'; + $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent3 = clone $vevent; + $vevent3->DURATION = 'P1D'; + $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent4 = clone $vevent; + $vevent4->DTSTART = '20111225'; + $vevent4->DTSTART['VALUE'] = 'DATE'; + $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + // Event with no end date should be treated as lasting the entire day. + $tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true); + // DTEND is non inclusive so all day events should not be returned on the next day. + $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00'), new \DateTime('2011-12-26 17:00:00'), false); + // The timezone of timerange in question also needs to be considered. + $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2011-12-26 17:00:00', new \DateTimeZone('Europe/Berlin')), false); + + $vevent5 = clone $vevent; + $vevent5->DURATION = 'P1D'; + $vevent5->RRULE = 'FREQ=YEARLY'; + $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + $tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true); + + $vevent6 = clone $vevent; + $vevent6->DTSTART = '20111225'; + $vevent6->DTSTART['VALUE'] = 'DATE'; + $vevent6->DTEND = '20111225'; + $vevent6->DTEND['VALUE'] = 'DATE'; + + $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + // Added this test to ensure that recurrence rules with no DTEND also + // get checked for the entire day. + $vevent7 = clone $vevent; + $vevent7->DTSTART = '20120101'; + $vevent7->DTSTART['VALUE'] = 'DATE'; + $vevent7->RRULE = 'FREQ=MONTHLY'; + $tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true); + // The timezone of timerange in question should also be considered. + + // Added this test to check recurring events that have no instances. + $vevent8 = clone $vevent; + $vevent8->DTSTART = '20130329T140000'; + $vevent8->DTEND = '20130329T153000'; + $vevent8->RRULE = array('FREQ' => 'WEEKLY', 'BYDAY' => array('FR'), 'UNTIL' => '20130412T115959Z'); + $vevent8->add('EXDATE', '20130405T140000'); + $vevent8->add('EXDATE', '20130329T140000'); + $tests[] = array($vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false); + + return $tests; + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php b/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php @@ -0,0 +1,66 @@ +VFREEBUSY; + + $tz = new \DateTimeZone('UTC'); + + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz))); + + // Checking whether the end time is treated as non-inclusive + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz))); + + } + + public function testValidate() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php b/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php @@ -0,0 +1,101 @@ +assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + public function testValidate() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + public function testValidateBroken() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals( + array("URL MUST NOT appear more than once in a VJOURNAL component"), + $messages + ); + + } + + public function timeRangeTestData() { + + $calendar = new VCalendar(); + + $tests = array(); + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->DTSTART = '20111223T120000Z'; + $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vjournal2 = $calendar->createComponent('VJOURNAL'); + $vjournal2->DTSTART = '20111223'; + $vjournal2->DTSTART['VALUE'] = 'DATE'; + $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vjournal3 = $calendar->createComponent('VJOURNAL'); + $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false); + $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + return $tests; + } + + + +} + diff --git a/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php b/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php @@ -0,0 +1,57 @@ +validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + function testGetTimeZone() { + + $input = <<assertEquals( + $tz, + $obj->VTIMEZONE->getTimeZone() + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php b/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php @@ -0,0 +1,180 @@ +assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + $vtodo = $calendar->createComponent('VTODO'); + $vtodo->DTSTART = '20111223T120000Z'; + $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo2 = clone $vtodo; + $vtodo2->DURATION = 'P1D'; + $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo3 = clone $vtodo; + $vtodo3->DUE = '20111225'; + $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DUE = '20111225'; + $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo5 = $calendar->createComponent('VTODO'); + $vtodo5->COMPLETED = '20111225'; + $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo6 = $calendar->createComponent('VTODO'); + $vtodo6->CREATED = '20111225'; + $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo7 = $calendar->createComponent('VTODO'); + $vtodo7->CREATED = '20111225'; + $vtodo7->COMPLETED = '20111226'; + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo7 = $calendar->createComponent('VTODO'); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true); + + return $tests; + + } + + public function testValidate() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + public function testValidateInvalid() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "UID MUST appear exactly once in a VTODO component", + "DTSTAMP MUST appear exactly once in a VTODO component", + ), $messages); + + } + + public function testValidateDUEDTSTARTMisMatch() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART", + ), $messages); + + } + + public function testValidateDUEbeforeDTSTART() { + + $input = <<validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "DUE must occur after DTSTART", + ), $messages); + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/ComponentTest.php b/vendor/sabre/vobject/tests/VObject/ComponentTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ComponentTest.php @@ -0,0 +1,528 @@ +createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $count = 0; + foreach($comp->children() as $key=>$subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent); + + } + $this->assertEquals(2,$count); + $this->assertEquals(1,$key); + + } + + function testMagicGet() { + + $comp = new VCalendar(array(), false); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $event = $comp->vevent; + $this->assertInstanceOf('Sabre\\VObject\\Component', $event); + $this->assertEquals('VEVENT', $event->name); + + $this->assertInternalType('null', $comp->vjournal); + + } + + function testMagicGetGroups() { + + $comp = new VCard(); + + $sub = $comp->createProperty('GROUP1.EMAIL','1@1.com'); + $comp->add($sub); + + $sub = $comp->createProperty('GROUP2.EMAIL','2@2.com'); + $comp->add($sub); + + $sub = $comp->createProperty('EMAIL','3@3.com'); + $comp->add($sub); + + $emails = $comp->email; + $this->assertEquals(3, count($emails)); + + $email1 = $comp->{"group1.email"}; + $this->assertEquals('EMAIL', $email1[0]->name); + $this->assertEquals('GROUP1', $email1[0]->group); + + $email3 = $comp->{".email"}; + $this->assertEquals('EMAIL', $email3[0]->name); + $this->assertEquals(null, $email3[0]->group); + + } + + function testMagicIsset() { + + $comp = new VCalendar(); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $this->assertTrue(isset($comp->vevent)); + $this->assertTrue(isset($comp->vtodo)); + $this->assertFalse(isset($comp->vjournal)); + + } + + function testMagicSetScalar() { + + $comp = new VCalendar(); + $comp->myProp = 'myValue'; + + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP); + $this->assertEquals('myValue',(string)$comp->MYPROP); + + + } + + function testMagicSetScalarTwice() { + + $comp = new VCalendar(array(), false); + $comp->myProp = 'myValue'; + $comp->myProp = 'myValue'; + + $this->assertEquals(1,count($comp->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP); + $this->assertEquals('myValue',(string)$comp->MYPROP); + + } + + function testMagicSetArray() { + + $comp = new VCalendar(); + $comp->ORG = array('Acme Inc', 'Section 9'); + + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->ORG); + $this->assertEquals(array('Acme Inc', 'Section 9'),$comp->ORG->getParts()); + + } + + function testMagicSetComponent() { + + $comp = new VCalendar(); + + // Note that 'myProp' is ignored here. + $comp->myProp = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp)); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testMagicSetTwice() { + + $comp = new VCalendar(array(), false); + + $comp->VEVENT = $comp->createComponent('VEVENT'); + $comp->VEVENT = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testArrayAccessGet() { + + $comp = new VCalendar(array(), false); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertEquals(2,count($comp->children())); + $this->assertTrue($comp->vevent[1] instanceof Component); + $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary); + + } + + function testArrayAccessExists() { + + $comp = new VCalendar(); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertTrue(isset($comp->vevent[0])); + $this->assertTrue(isset($comp->vevent[1])); + + } + + /** + * @expectedException LogicException + */ + function testArrayAccessSet() { + + $comp = new VCalendar(); + $comp['hey'] = 'hi there'; + + } + /** + * @expectedException LogicException + */ + function testArrayAccessUnset() { + + $comp = new VCalendar(); + unset($comp[0]); + + } + + function testAddScalar() { + + $comp = new VCalendar(array(), false); + + $comp->add('myprop','value'); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children[0]; + + $this->assertTrue($bla instanceof Property); + $this->assertEquals('MYPROP',$bla->name); + $this->assertEquals('value',(string)$bla); + + } + + function testAddScalarParams() { + + $comp = new VCalendar(array(), false); + + $comp->add('myprop','value',array('param1'=>'value1')); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children[0]; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $bla); + $this->assertEquals('MYPROP',$bla->name); + $this->assertEquals('value', (string)$bla); + + $this->assertEquals(1, count($bla->parameters())); + + $this->assertEquals('PARAM1',$bla->parameters['PARAM1']->name); + $this->assertEquals('value1',$bla->parameters['PARAM1']->getValue()); + + } + + + function testAddComponent() { + + $comp = new VCalendar(array(), false); + + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testAddComponentTwice() { + + $comp = new VCalendar(array(), false); + + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(2, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail() { + + $comp = new VCalendar(); + $comp->add($comp->createComponent('VEVENT'),'hello'); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail2() { + + $comp = new VCalendar(); + $comp->add(array()); + + } + + function testMagicUnset() { + + $comp = new VCalendar(array(), false); + $comp->add($comp->createComponent('VEVENT')); + + unset($comp->vevent); + + $this->assertEquals(0, count($comp->children())); + + } + + + function testCount() { + + $comp = new VCalendar(); + $this->assertEquals(1,$comp->count()); + + } + + function testChildren() { + + $comp = new VCalendar(array(), false); + + // Note that 'myProp' is ignored here. + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->children(); + $this->assertInternalType('array', $r); + $this->assertEquals(2,count($r)); + } + + function testGetComponents() { + + $comp = new VCalendar(); + + $comp->add($comp->createProperty('FOO','BAR')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->getComponents(); + $this->assertInternalType('array', $r); + $this->assertEquals(1, count($r)); + $this->assertEquals('VTODO', $r[0]->name); + } + + function testSerialize() { + + $comp = new VCalendar(array(), false); + $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize()); + + } + + function testSerializeChildren() { + + $comp = new VCalendar(array(), false); + $event = $comp->add($comp->createComponent('VEVENT')); + unset($event->DTSTAMP, $event->UID); + $comp->add($comp->createComponent('VTODO')); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str); + + } + + function testSerializeOrderCompAndProp() { + + $comp = new VCalendar(array(), false); + $comp->add($event = $comp->createComponent('VEVENT')); + $comp->add('PROP1','BLABLA'); + $comp->add('VERSION','2.0'); + $comp->add($comp->createComponent('VTIMEZONE')); + + unset($event->DTSTAMP, $event->UID); + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str); + + } + + function testAnotherSerializeOrderProp() { + + $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'); + + $comp = new VCard(array(), false); + + $comp->__set('SOMEPROP','FOO'); + $comp->__set('ANOTHERPROP','FOO'); + $comp->__set('THIRDPROP','FOO'); + foreach ($prop4s as $prop4) { + $comp->add('PROP4', 'FOO '.$prop4); + } + $comp->__set('PROPNUMBERFIVE', 'FOO'); + $comp->__set('PROPNUMBERSIX', 'FOO'); + $comp->__set('PROPNUMBERSEVEN', 'FOO'); + $comp->__set('PROPNUMBEREIGHT', 'FOO'); + $comp->__set('PROPNUMBERNINE', 'FOO'); + $comp->__set('PROPNUMBERTEN', 'FOO'); + $comp->__set('VERSION','2.0'); + $comp->__set('UID', 'FOO'); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str); + + } + + function testInstantiateWithChildren() { + + $comp = new VCard(array( + 'ORG' => array('Acme Inc.', 'Section 9'), + 'FN' => 'Finn The Human', + )); + + $this->assertEquals(array('Acme Inc.', 'Section 9'), $comp->ORG->getParts()); + $this->assertEquals('Finn The Human', $comp->FN->getValue()); + + } + + function testInstantiateSubComponent() { + + $comp = new VCalendar(); + $event = $comp->createComponent('VEVENT', array( + $comp->createProperty('UID', '12345'), + )); + $comp->add($event); + + $this->assertEquals('12345', $comp->VEVENT->UID->getValue()); + + } + + function testRemoveByName() { + + $comp = new VCalendar(array(), false); + $comp->add('prop1','val1'); + $comp->add('prop2','val2'); + $comp->add('prop2','val2'); + + $comp->remove('prop2'); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + function testRemoveByObj() { + + $comp = new VCalendar(array(), false); + $comp->add('prop1','val1'); + $prop = $comp->add('prop2','val2'); + + $comp->remove($prop); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testRemoveNotFound() { + + $comp = new VCalendar(array(), false); + $prop = $comp->createProperty('A','B'); + $comp->remove($prop); + + } + + /** + * @dataProvider ruleData + */ + function testValidateRules($componentList, $errorCount) { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard,'Hi', array(), $defaults = false ); + foreach($componentList as $v) { + $component->add($v,'Hello.'); + } + + $this->assertEquals($errorCount, count($component->validate())); + + } + + function testValidateRepair() { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard,'Hi', array(), $defaults = false ); + $component->validate(Component::REPAIR); + $this->assertEquals('yow', $component->BAR->getValue()); + + } + + function ruleData() { + + return array( + + array(array(), 2), + array(array('FOO'), 3), + array(array('BAR'), 1), + array(array('BAZ'), 1), + array(array('BAR','BAZ'), 0), + array(array('BAR','BAZ','ZIM',), 0), + array(array('BAR','BAZ','ZIM','GIR'), 0), + array(array('BAR','BAZ','ZIM','GIR','GIR'), 1), + + ); + + } + +} + +class FakeComponent extends Component { + + public function getValidationRules() { + + return array( + 'FOO' => '0', + 'BAR' => '1', + 'BAZ' => '+', + 'ZIM' => '*', + 'GIR' => '?', + ); + + } + + public function getDefaults() { + + return array( + 'BAR' => 'yow', + ); + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php b/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php @@ -0,0 +1,417 @@ +assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true)); + $this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D',true)); + $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true)); + $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true)); + $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true)); + $this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S',true)); + $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S')); + + } + + function testParseICalendarDurationDateInterval() { + + $expected = new DateInterval('P7D'); + $this->assertEquals($expected, DateTimeParser::parseDuration('P1W')); + $this->assertEquals($expected, DateTimeParser::parse('P1W')); + + $expected = new DateInterval('PT3M'); + $expected->invert = true; + $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M')); + + } + + /** + * @expectedException LogicException + */ + function testParseICalendarDurationFail() { + + DateTimeParser::parseDuration('P1X',true); + + } + + function testParseICalendarDateTime() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405'); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC')); + + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + * @expectedException LogicException + */ + function testParseICalendarDateTimeBadFormat() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405 '); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405Z'); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC2() { + + $dateTime = DateTimeParser::parseDateTime('20101211T160000Z'); + + $compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeCustomTimeZone() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam')); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('Europe/Amsterdam')); + $this->assertEquals($compare, $dateTime); + + } + + function testParseICalendarDate() { + + $dateTime = DateTimeParser::parseDate('20100316'); + + $expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('20100316'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events + */ + function testParseICalendarDateGreaterThan4000() { + + $dateTime = DateTimeParser::parseDate('45001231'); + + $expected = new DateTime('4500-12-31 00:00:00',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events + */ + function testParseICalendarDateTimeGreaterThan4000() { + + $dateTime = DateTimeParser::parseDateTime('45001231T235959'); + + $expected = new DateTime('4500-12-31 23:59:59',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231T235959'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * @depends testParseICalendarDate + * @expectedException LogicException + */ + function testParseICalendarDateBadFormat() { + + $dateTime = DateTimeParser::parseDate('20100316T141405'); + + } + + /** + * @dataProvider vcardDates + */ + function testVCardDate($input, $output) { + + $this->assertEquals( + $output, + DateTimeParser::parseVCardDateTime($input) + ); + + } + + /** + * @dataProvider vcardDates + * @expectedException \InvalidArgumentException + */ + function testBadVCardDate() { + + DateTimeParser::parseVCardDateTime('1985---01'); + + } + + /** + * @dataProvider vcardDates + * @expectedException \InvalidArgumentException + */ + function testBadVCardTime() { + + DateTimeParser::parseVCardTime('23:12:166'); + + } + + function vcardDates() { + + return array( + array( + "19961022T140000", + array( + "year" => 1996, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => 00, + "timezone" => null + ), + ), + array( + "--1022T1400", + array( + "year" => null, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => null, + "timezone" => null + ), + ), + array( + "---22T14", + array( + "year" => null, + "month" => null, + "date" => 22, + "hour" => 14, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "19850412", + array( + "year" => 1985, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "1985-04", + array( + "year" => 1985, + "month" => 04, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "1985", + array( + "year" => 1985, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "--0412", + array( + "year" => null, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "---12", + array( + "year" => null, + "month" => null, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "T102200", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 0, + "timezone" => null + ), + ), + array( + "T1022", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => null, + "timezone" => null + ), + ), + array( + "T10", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "T-2200", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => 22, + "second" => 00, + "timezone" => null + ), + ), + array( + "T--00", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => 00, + "timezone" => null + ), + ), + array( + "T102200Z", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => 'Z' + ), + ), + array( + "T102200-0800", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => '-0800' + ), + ), + + // extended format + array( + "2012-11-29T15:10:53Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + // with milliseconds + array( + "20121129T151053.123Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + // extended format with milliseconds + array( + "2012-11-29T15:10:53.123Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/DocumentTest.php b/vendor/sabre/vobject/tests/VObject/DocumentTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/DocumentTest.php @@ -0,0 +1,70 @@ +assertEquals(Document::UNKNOWN, $doc->getDocumentType()); + + } + + function testConstruct() { + + $doc = new MockDocument('VLIST'); + $this->assertEquals('VLIST', $doc->name); + + } + + function testCreateComponent() { + + $vcal = new Component\VCalendar(array(), false); + + $event = $vcal->createComponent('VEVENT'); + + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $vcal->add($event); + + $prop = $vcal->createProperty('X-PROP','1234256',array('X-PARAM' => '3')); + $this->assertInstanceOf('Sabre\VObject\Property', $prop); + + $event->add($prop); + + unset( + $event->DTSTAMP, + $event->UID + ); + + $out = $vcal->serialize(); + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out); + + } + + function testCreate() { + + $vcal = new Component\VCalendar(array(), false); + + $event = $vcal->create('VEVENT'); + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + + $event = $vcal->create('CALSCALE'); + $this->assertInstanceOf('Sabre\VObject\Property\Text', $event); + + } + + function testGetClassNameForPropertyValue() { + + $vcal = new Component\VCalendar(array(), false); + $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT')); + $this->assertNull($vcal->getClassNameForPropertyValue('FOO')); + + } + +} + + +class MockDocument extends Document { + +} diff --git a/vendor/sabre/vobject/tests/VObject/ElementListTest.php b/vendor/sabre/vobject/tests/VObject/ElementListTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ElementListTest.php @@ -0,0 +1,33 @@ +createComponent('VEVENT'); + + $elems = array( + $sub, + clone $sub, + clone $sub + ); + + $elemList = new ElementList($elems); + + $count = 0; + foreach($elemList as $key=>$subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent); + + } + $this->assertEquals(3,$count); + $this->assertEquals(2,$key); + + } + + +} diff --git a/vendor/sabre/vobject/tests/VObject/EmClientTest.php b/vendor/sabre/vobject/tests/VObject/EmClientTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/EmClientTest.php @@ -0,0 +1,55 @@ +VEVENT->DTSTART->getDateTime(); + $this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt); + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php b/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php @@ -0,0 +1,69 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $this->assertTrue(isset($converted->EMAIL['X-INTERN'])); + + $version = Version::VERSION; + + $expected = <<assertEquals($expected, str_replace("\r","", $vcard)); + + } + + function testVCard21Parameter() { + + $vcard = new Component\VCard(array(), false); + $vcard->VERSION = '2.1'; + $vcard->PHOTO = 'random_stuff'; + $vcard->PHOTO->add(null,'BASE64'); + $vcard->UID = 'foo-bar'; + + $result = $vcard->serialize(); + $expected = array( + "BEGIN:VCARD", + "VERSION:2.1", + "PHOTO;BASE64:" . base64_encode('random_stuff'), + "UID:foo-bar", + "END:VCARD", + "", + ); + + $this->assertEquals(implode("\r\n", $expected), $result); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php b/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php @@ -0,0 +1,31 @@ +assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); + + } + +} + diff --git a/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php b/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php @@ -0,0 +1,394 @@ +getResult(); + + $expected = (array)$expected; + + $freebusy = $result->VFREEBUSY->select('FREEBUSY'); + + foreach($freebusy as $fb) { + + $this->assertContains((string)$fb, $expected, "$fb did not appear in our list of expected freebusy strings. This is concerning!"); + + $k = array_search((string)$fb, $expected); + unset($expected[$k]); + + } + $this->assertTrue( + count($expected) === 0, + 'There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize() + ); + + } + + function testGeneratorBaseObject() { + + $obj = new Component\VCalendar(); + $obj->METHOD = 'PUBLISH'; + + $gen = new FreeBusyGenerator(); + $gen->setObjects(array()); + $gen->setBaseObject($obj); + + $result = $gen->getResult(); + + $this->assertEquals('PUBLISH', $result->METHOD->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg() { + + $gen = new FreeBusyGenerator( + new \DateTime('2012-01-01'), + new \DateTime('2012-12-31'), + new \StdClass() + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php b/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php @@ -0,0 +1,31 @@ +assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php b/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php @@ -0,0 +1,31 @@ +VEVENT->ATTACH; + + $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop); + $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue()); + + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -0,0 +1,1130 @@ + 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringReply() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringAllDay() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testNoChange() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected); + + } + + function testNoChangeForceSend() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testNoRelevantAttendee() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, an event exists in an attendees calendar. The event + * is recurring, and the attendee deletes 1 instance of the event. + * This instance shows up in EXDATE + * + * This should automatically generate a DECLINED message for that + * specific instance. + */ + function testCreateReplyByException() { + + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * This test is identical to the last, but now we're working with + * timezones. + * + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionTz() { + + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionAllDay() { + + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testDeclined() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + function testDeclinedCancelledEvent() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, a new exception is created by an attendee as well. + * + * Except in this case, there was already an overridden event, and the + * overridden event was marked as cancelled by the attendee. + * + * For any other attendence status, the new status would have been + * declined, but for this, no message should we sent. + */ + function testDontCreateReplyWhenEventWasDeclined() { + + + $oldMessage = <<parse($oldMessage, $newMessage, $expected); + + } + + function testScheduleAgentOnOrganizer() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected); + + } + + function testAcceptedAllDay() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * This function tests an attendee updating their status to an event where + * they don't have the master event of. + * + * This is possible in cases an organizer created a recurring event, and + * invited an attendee for one instance of the event. + */ + function testReplyNoMasterEvent() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } + + /** + * A party crasher is an attendee that accepted an event, but was not in + * any original invite. + * + * @depends testAccepted + */ + function testPartyCrasher() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php @@ -0,0 +1,340 @@ + 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testOrganizerDeleteWithDuration() { + + $oldMessage = << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testAttendeeDeleteWithDtend() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeDeleteWithDuration() { + + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeDeleteCancelledEvent() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testNoCalendar() { + + $this->parse(null, null, array(), 'mailto:one@example.org'); + + } + + function testVTodo() { + + $oldMessage = <<parse($oldMessage, null, array(), 'mailto:one@example.org'); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php @@ -0,0 +1,528 @@ +parse($message); + + } + + function testVTODO() { + + $message = <<parse($message); + + } + + function testSimpleInvite() { + + $message = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:white@example.org', + 'recipientName' => 'White', + 'message' => $expectedMessage, + ), + ); + + $result = $this->parse($message, $expected); + + } + + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventUIDMisMatch() { + + $message = <<parse($message, array()); + + } + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventOrganizerMisMatch() { + + $message = <<parse($message, array()); + + } + + function testRecurrenceInvite() { + + $message = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<parse($message, $expected); + + } + + function testRecurrenceInvite2() { + + // This method tests a nearly identical path, but in this case the + // master event does not have an EXDATE. + $message = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<parse($message, $expected); + + } + + function testScheduleAgentClient() { + + $message = <<parse($message, $expected); + + } + + /** + * @expectedException Sabre\VObject\ITip\ITipException + */ + function testMultipleUID() { + + $message = <<parse($message, array()); + + } + + /** + * @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException + * + */ + function testChangingOrganizers() { + + $message = <<parse($message, array()); + + } + function testNoOrganizerHasAttendee() { + + $message = <<parse($message, array()); + + } + + function parse($message, $expected = array()) { + + $broker = new Broker(); + $result = $broker->parseEvent($message, 'mailto:strunk@example.org'); + + $this->assertEquals(count($expected), count($result)); + + foreach($expected as $index=>$ex) { + + $message = $result[$index]; + + foreach($ex as $key=>$val) { + + if ($key==='message') { + $this->assertEquals( + str_replace("\n", "\r\n", $val), + rtrim($message->message->serialize(), "\r\n") + ); + } else { + $this->assertEquals($val, $message->$key); + } + + } + + } + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php @@ -0,0 +1,168 @@ +process($itip, null, $expected); + + } + + function testRequestUpdate() { + + $itip = <<process($itip, $old, $expected); + + } + + function testCancel() { + + $itip = <<process($itip, $old, $expected); + + } + + function testCancelNoExistingEvent() { + + $itip = <<process($itip, $old, $expected); + + } + + function testUnsupportedComponent() { + + $itip = <<process($itip, $old, $expected); + + } + + function testUnsupportedMethod() { + + $itip = <<process($itip, $old, $expected); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -0,0 +1,496 @@ +process($itip, $old, $expected); + + } + + function testReplyAccept() { + + $itip = <<process($itip, $old, $expected); + + } + + function testReplyRequestStatus() { + + $itip = <<process($itip, $old, $expected); + + } + + + function testReplyPartyCrasher() { + + $itip = <<process($itip, $old, $expected); + + } + + function testReplyNewException() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + + } + + function testReplyNewExceptionTz() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + + } + + function testReplyPartyCrashCreateExcepton() { + + // IN this test there's a recurring event that has an exception. The + // exception is missing the attendee. + // + // The attendee party crashes the instance, so it should show up in the + // resulting object. + $itip = <<process($itip, $old, $expected); + + } + + function testReplyNewExceptionNoMasterEvent() { + + /** + * This iTip message would normally create a new exception, but the + * server is not able to create this new instance, because there's no + * master event to clone from. + * + * This test checks if the message is ignored. + */ + $itip = <<process($itip, $old, $expected); + + } + + /** + * @depends testReplyAccept + */ + function testReplyAcceptUpdateRSVP() { + + $itip = <<process($itip, $old, $expected); + + } + + function testReplyNewExceptionFirstOccurence() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php @@ -0,0 +1,103 @@ +parseEvent($newMessage, $currentUser, $oldMessage); + + $this->assertEquals(count($expected), count($result)); + + foreach($expected as $index=>$ex) { + + $message = $result[$index]; + + foreach($ex as $key=>$val) { + + if ($key==='message') { + $this->assertVObjEquals( + $val, + $message->message->serialize() + ); + } else { + $this->assertEquals($val, $message->$key); + } + + } + + } + + } + + function process($input, $existingObject = null, $expected = false) { + + $version = \Sabre\VObject\Version::VERSION; + + $vcal = Reader::read($input); + + foreach($vcal->getComponents() as $mainComponent) { + break; + } + + $message = new Message(); + $message->message = $vcal; + $message->method = isset($vcal->METHOD)?$vcal->METHOD->getValue():null; + $message->component = $mainComponent->name; + $message->uid = $mainComponent->uid->getValue(); + $message->sequence = isset($vcal->VEVENT[0])?(string)$vcal->VEVENT[0]->SEQUENCE:null; + + if ($message->method === 'REPLY') { + + $message->sender = $mainComponent->ATTENDEE->getValue(); + $message->senderName = isset($mainComponent->ATTENDEE['CN'])?$mainComponent->ATTENDEE['CN']->getValue():null; + $message->recipient = $mainComponent->ORGANIZER->getValue(); + $message->recipientName = isset($mainComponent->ORGANIZER['CN'])?$mainComponent->ORGANIZER['CN']:null; + + } + + $broker = new Broker(); + + if (is_string($existingObject)) { + $existingObject = str_replace( + '%foo%', + "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN", + $existingObject + ); + $existingObject = Reader::read($existingObject); + } + + $result = $broker->processMessage($message, $existingObject); + + if (is_string($expected)) { + $expected = str_replace( + '%foo%', + "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN", + $expected + ); + $expected = str_replace("\n", "\r\n", $expected); + + } + if ($result instanceof \Sabre\VObject\Component\VCalendar) { + $result = $result->serialize(); + $result = rtrim($result,"\r\n"); + } + + $this->assertEquals( + $expected, + $result + ); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php @@ -0,0 +1,77 @@ +setAccessible(true); + $data = $reflectionMethod->invoke($broker, $calendar); + $this->assertInstanceOf('DateTimeZone', $data['timezone']); + $this->assertEquals($data['timezone']->getName(), 'Europe/Minsk'); + } +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -0,0 +1,841 @@ + 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => false, + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromNonSchedulingToSchedulingObject() { + + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromSchedulingToNonSchedulingObject() { + + $oldMessage = << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testNoAttendees() { + + $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testRemoveInstance() { + + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + /** + * This test is identical to the first test, except this time we change the + * DURATION property. + * + * This should ensure that the message is significant for every attendee, + */ + function testInviteChangeSignificantChange() { + + $oldMessage = << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChange() { + + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChangeForceSend() { + + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteRemoveAttendees() { + + $oldMessage = << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeExdateOrder() { + + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php b/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php @@ -0,0 +1,2653 @@ + '20140813T153116Z-12176-1000-1065-6@johnny-lubuntu', + 'method' => 'REQUEST', + 'sender' => 'mailto:martin@fruux.com', + 'senderName' => null, + 'recipient' => 'mailto:dominik@fruux.com', + 'recipientName' => null, + 'message' => $expectedICS, + ) + ); + $this->parse(null, $ics, $expected, 'mailto:martin@fruux.com'); + + } + + /** + * This is an event originally from evolution, then parsed by sabredav and + * again mangled by iCal. This triggered a few bugs related to email + * address scheme casing. + */ + public function testAttendeeModify() { + + $old = <<parse($old, $new, array(), 'mailto:a1@example.org'); + + + } + + +} diff --git a/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php b/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php @@ -0,0 +1,32 @@ +assertFalse($message->getScheduleStatus()); + + } + + public function testScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '1.2;Delivered'; + + $this->assertEquals('1.2', $message->getScheduleStatus()); + + } + + public function testUnexpectedScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '9.9.9'; + + $this->assertEquals('9.9.9', $message->getScheduleStatus()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue153Test.php b/vendor/sabre/vobject/tests/VObject/Issue153Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue153Test.php @@ -0,0 +1,14 @@ +assertEquals('Test Benutzer', (string)$obj->fn); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue26Test.php b/vendor/sabre/vobject/tests/VObject/Issue26Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue26Test.php @@ -0,0 +1,36 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, 'bae5d57a98'); + iterator_to_array($it); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php b/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php @@ -0,0 +1,39 @@ +assertInstanceOf('Sabre\\VObject\\Recur\EventIterator', $it); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue40Test.php b/vendor/sabre/vobject/tests/VObject/Issue40Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue40Test.php @@ -0,0 +1,30 @@ +add('N', array('van der Harten', array('Rene','J.'), "", 'Sir','R.D.O.N.'), array('SORT-AS' => array('Harten','Rene'))); + + $expected = implode("\r\n", array( + "BEGIN:VCARD", + "VERSION:3.0", + "PRODID:-//Sabre//Sabre VObject " . Version::VERSION . '//EN', + "N;SORT-AS=Harten,Rene:van der Harten;Rene,J.;;Sir;R.D.O.N.", + "END:VCARD", + "" + )); + + $this->assertEquals($expected, $card->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue64Test.php b/vendor/sabre/vobject/tests/VObject/Issue64Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue64Test.php @@ -0,0 +1,19 @@ +convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Issue96Test.php b/vendor/sabre/vobject/tests/VObject/Issue96Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Issue96Test.php @@ -0,0 +1,24 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertEquals("http://www.example.org", $vcard->url->getValue()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/JCalTest.php b/vendor/sabre/vobject/tests/VObject/JCalTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/JCalTest.php @@ -0,0 +1,150 @@ +add('VEVENT', array( + "UID" => "foo", + "DTSTART" => new \DateTime("2013-05-26 18:10:00Z"), + "DURATION" => "P1D", + "CATEGORIES" => array('home', 'testing'), + "CREATED" => new \DateTime("2013-05-26 18:10:00Z"), + + "ATTENDEE" => "mailto:armin@example.org", + "GEO" => array(51.96668, 7.61876), + "SEQUENCE" => 5, + "FREEBUSY" => array("20130526T210213Z/PT1H", "20130626T120000Z/20130626T130000Z"), + "URL" => "http://example.org/", + "TZOFFSETFROM" => "+05:00", + "RRULE" => array('FREQ' => 'WEEKLY', 'BYDAY' => array('MO','TU')), + )); + + // Modifying DTSTART to be a date-only. + $event->dtstart['VALUE'] = 'DATE'; + $event->add("X-BOOL", true, array('VALUE' => 'BOOLEAN')); + $event->add("X-TIME", "08:00:00", array('VALUE' => 'TIME')); + $event->add("ATTACH", "attachment", array('VALUE' => 'BINARY')); + $event->add("ATTENDEE", "mailto:dominik@example.org", array("CN" => "Dominik", "PARTSTAT" => "DECLINED")); + + $event->add('REQUEST-STATUS', array("2.0", "Success")); + $event->add('REQUEST-STATUS', array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org")); + + $event->add('DTEND', '20150108T133000'); + + $expected = array( + "vcalendar", + array( + array( + "version", + new \StdClass(), + "text", + "2.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ), + array( + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ), + ), + array( + array("vevent", + array( + array( + "uid", new \StdClass(), "text", "foo", + ), + array( + "dtstart", new \StdClass(), "date", "2013-05-26", + ), + array( + "duration", new \StdClass(), "duration", "P1D", + ), + array( + "categories", new \StdClass(), "text", "home", "testing", + ), + array( + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ), + + array( + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ), + array( + "geo", new \StdClass(), "float", array(51.96668, 7.61876), + ), + array( + "sequence", new \StdClass(), "integer", 5 + ), + array( + "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"), + ), + array( + "url", new \StdClass(), "uri", "http://example.org/", + ), + array( + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ), + array( + "rrule", new \StdClass(), "recur", array( + 'freq' => 'WEEKLY', + 'byday' => array('MO', 'TU'), + ), + ), + array( + "x-bool", new \StdClass(), "boolean", true + ), + array( + "x-time", new \StdClass(), "time", "08:00:00", + ), + array( + "attach", new \StdClass(), "binary", base64_encode('attachment') + ), + array( + "attendee", + (object)array( + "cn" => "Dominik", + "partstat" => "DECLINED", + ), + "cal-address", + "mailto:dominik@example.org" + ), + array( + "request-status", + new \StdClass(), + "text", + array("2.0", "Success"), + ), + array( + "request-status", + new \StdClass(), + "text", + array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"), + ), + array( + 'dtend', + new \StdClass(), + "date-time", + "2015-01-08T13:30:00", + ), + ), + array(), + ) + ), + ); + + $this->assertEquals($expected, $cal->jsonSerialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/JCardTest.php b/vendor/sabre/vobject/tests/VObject/JCardTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/JCardTest.php @@ -0,0 +1,195 @@ + "4.0", + "UID" => "foo", + "BDAY" => "19850407", + "REV" => "19951031T222710Z", + "LANG" => "nl", + "N" => array("Last", "First", "Middle", "", ""), + "item1.TEL" => "+1 555 123456", + "item1.X-AB-LABEL" => "Walkie Talkie", + "ADR" => array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + )); + + $card->add('BDAY', '1979-12-25', array('VALUE' => 'DATE', 'X-PARAM' => array(1,2))); + $card->add('BDAY', '1979-12-25T02:00:00', array('VALUE' => 'DATE-TIME')); + + + $card->add('X-TRUNCATED', '--1225', array('VALUE' => 'DATE')); + $card->add('X-TIME-LOCAL', '123000', array('VALUE' => 'TIME')); + $card->add('X-TIME-UTC', '12:30:00Z', array('VALUE' => 'TIME')); + $card->add('X-TIME-OFFSET', '12:30:00-08:00', array('VALUE' => 'TIME')); + $card->add('X-TIME-REDUCED', '23', array('VALUE' => 'TIME')); + $card->add('X-TIME-TRUNCATED', '--30', array('VALUE' => 'TIME')); + + $card->add('X-KARMA-POINTS', '42', array('VALUE' => 'INTEGER')); + $card->add('X-GRADE', '1.3', array('VALUE' => 'FLOAT')); + + $card->add('TZ', '-05:00', array('VALUE' => 'UTC-OFFSET')); + + $expected = array( + "vcard", + array( + array( + "version", + new \StdClass(), + "text", + "4.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ), + array( + "uid", + new \StdClass(), + "text", + "foo", + ), + array( + "bday", + new \StdClass(), + "date-and-or-time", + "1985-04-07", + ), + array( + "rev", + new \StdClass(), + "timestamp", + "1995-10-31T22:27:10Z", + ), + array( + "lang", + new \StdClass(), + "language-tag", + "nl", + ), + array( + "n", + new \StdClass(), + "text", + array("Last", "First", "Middle", "", ""), + ), + array( + "tel", + (object)array( + "group" => "item1", + ), + "text", + "+1 555 123456", + ), + array( + "x-ab-label", + (object)array( + "group" => "item1", + ), + "unknown", + "Walkie Talkie", + ), + array( + "adr", + new \StdClass(), + "text", + array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + ), + array( + "bday", + (object)array( + 'x-param' => array(1,2), + ), + "date", + "1979-12-25", + ), + array( + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ), + array( + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ), + array( + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ), + array( + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ), + array( + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ), + array( + "x-time-reduced", + new \StdClass(), + "time", + "23" + ), + array( + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ), + array( + "x-karma-points", + new \StdClass(), + "integer", + 42 + ), + array( + "x-grade", + new \StdClass(), + "float", + 1.3 + ), + array( + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ), + ), + ); + + $this->assertEquals($expected, $card->jsonSerialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php b/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php @@ -0,0 +1,23 @@ +assertEquals($event, $obj->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/ParameterTest.php b/vendor/sabre/vobject/tests/VObject/ParameterTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ParameterTest.php @@ -0,0 +1,135 @@ +assertEquals('NAME',$param->name); + $this->assertEquals('value',$param->getValue()); + + } + + function testSetupNameLess() { + + $card = new Component\VCard(); + + $param = new Parameter($card, null,'URL'); + $this->assertEquals('VALUE',$param->name); + $this->assertEquals('URL',$param->getValue()); + $this->assertTrue($param->noName); + + } + + function testModify() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', null); + $param->addValue(1); + $this->assertEquals(array(1), $param->getParts()); + + $param->setParts(array(1,2)); + $this->assertEquals(array(1,2), $param->getParts()); + + $param->addValue(3); + $this->assertEquals(array(1,2,3), $param->getParts()); + + $param->setValue(4); + $param->addValue(5); + $this->assertEquals(array(4,5), $param->getParts()); + + } + + function testCastToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('value',$param->__toString()); + $this->assertEquals('value',(string)$param); + + } + + function testCastNullToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('',$param->__toString()); + $this->assertEquals('',(string)$param); + + } + + function testSerialize() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('NAME=value',$param->serialize()); + + } + + function testSerializeEmpty() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('NAME=',$param->serialize()); + + } + + function testSerializeComplex() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name',array("val1", "val2;", "val3^", "val4\n", "val5\"")); + $this->assertEquals('NAME=val1,"val2;","val3^^","val4^n","val5^\'"',$param->serialize()); + + } + + /** + * iCal 7.0 (OSX 10.9) has major issues with the EMAIL property, when the + * value contains a plus sign, and it's not quoted. + * + * So we specifically added support for that. + */ + function testSerializePlusSign() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'EMAIL',"user+something@example.org"); + $this->assertEquals('EMAIL="user+something@example.org"',$param->serialize()); + + } + + function testIterate() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', array(1,2,3,4)); + $result = array(); + + foreach($param as $value) { + $result[] = $value; + } + + $this->assertEquals(array(1,2,3,4), $result); + + } + + function testSerializeColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name','va:lue'); + $this->assertEquals('NAME="va:lue"',$param->serialize()); + + } + + function testSerializeSemiColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name','va;lue'); + $this->assertEquals('NAME="va;lue"',$param->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php b/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php @@ -0,0 +1,395 @@ + "item1", + ), + "text", + "+1 555 123456", + ), + array( + "x-ab-label", + (object)array( + "group" => "item1", + ), + "unknown", + "Walkie Talkie", + ), + array( + "adr", + new \StdClass(), + "text", + array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + ), + array( + "bday", + (object)array( + 'x-param' => array(1,2), + ), + "date", + "1979-12-25", + ), + array( + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ), + array( + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ), + array( + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ), + array( + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ), + array( + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ), + array( + "x-time-reduced", + new \StdClass(), + "time", + "23" + ), + array( + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ), + array( + "x-karma-points", + new \StdClass(), + "integer", + 42 + ), + array( + "x-grade", + new \StdClass(), + "float", + 1.3 + ), + array( + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ), + ), + ); + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + + $version = VObject\Version::VERSION; + + + $result = $vobj->serialize(); + $expected = <<assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testRoundTripJCal() { + + $input = array( + "vcalendar", + array( + array( + "version", + new \StdClass(), + "text", + "2.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN", + ), + array( + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ), + ), + array( + array("vevent", + array( + array( + "uid", new \StdClass(), "text", "foo", + ), + array( + "dtstart", new \StdClass(), "date", "2013-05-26", + ), + array( + "duration", new \StdClass(), "duration", "P1D", + ), + array( + "categories", new \StdClass(), "text", "home", "testing", + ), + array( + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ), + array( + "attach", new \StdClass(), "binary", base64_encode('attachment') + ), + array( + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ), + array( + "geo", new \StdClass(), "float", array(51.96668, 7.61876), + ), + array( + "sequence", new \StdClass(), "integer", 5 + ), + array( + "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"), + ), + array( + "url", new \StdClass(), "uri", "http://example.org/", + ), + array( + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ), + array( + "rrule", new \StdClass(), "recur", array( + 'freq' => 'WEEKLY', + 'byday' => array('MO', 'TU'), + ), + ), + array( + "x-bool", new \StdClass(), "boolean", true + ), + array( + "x-time", new \StdClass(), "time", "08:00:00", + ), + array( + "attendee", + (object)array( + "cn" => "Dominik", + "partstat" => "DECLINED", + ), + "cal-address", + "mailto:dominik@example.org" + ), + array( + "request-status", + new \StdClass(), + "text", + array("2.0", "Success"), + ), + array( + "request-status", + new \StdClass(), + "text", + array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"), + ), + ), + array( + array("valarm", + array( + array( + "action", new \StdClass(), "text", "DISPLAY", + ), + ), + array(), + ), + ), + ) + ), + ); + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + $result = $vobj->serialize(); + + $version = VObject\Version::VERSION; + + $expected = <<assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testParseStreamArg() { + + $input = array( + "vcard", + array( + array( + "FN", new \StdClass(), 'text', "foo", + ), + ), + ); + + $stream = fopen('php://memory','r+'); + fwrite($stream, json_encode($input)); + rewind($stream); + + $result = VObject\Reader::readJson($stream,0); + $this->assertEquals('foo', $result->FN->getValue()); + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseInvalidData() { + + $json = new Json(); + $input = array( + "vlist", + array( + array( + "FN", new \StdClass(), 'text', "foo", + ), + ), + ); + + $json->parse(json_encode($input), 0); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php b/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php @@ -0,0 +1,21 @@ +parse(fopen(__FILE__,'a')); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php b/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php @@ -0,0 +1,108 @@ +assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->label)); + + } + + function testReadQuotedPrintableNewlineSoft() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->label)); + + } + + function testReadQuotedPrintableNewlineHard() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->label)); + + + } + + function testReadQuotedPrintableCompatibilityMS() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD"; + $result = Reader::read($data, Reader::OPTION_FORGIVING); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->label)); + + } + + function testReadQuotesPrintableCompoundValues() { + + $data = <<assertEquals(array( + '','','Münster Str. 1','Münster','','48143','Deutschland' + ), $result->ADR->getParts()); + + + } + + private function getPropertyValue(\Sabre\VObject\Property $property) { + + return (string)$property; + + /* + $param = $property['encoding']; + if ($param !== null) { + $encoding = strtoupper((string)$param); + if ($encoding === 'QUOTED-PRINTABLE') { + $value = quoted_printable_decode($value); + } else { + throw new Exception(); + } + } + + $param = $property['charset']; + if ($param !== null) { + $charset = strtoupper((string)$param); + if ($charset !== 'UTF-8') { + $value = mb_convert_encoding($value, 'UTF-8', $charset); + } + } else { + $value = StringUtil::convertToUTF8($value); + } + + return $value; + */ + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php b/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php @@ -0,0 +1,19 @@ +add('PHOTO', array('a','b')); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php b/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php @@ -0,0 +1,22 @@ +assertTrue($vcard->{'X-AWESOME'}->getValue()); + $this->assertFalse($vcard->{'X-SUCKS'}->getValue()); + + $this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType()); + $this->assertEquals($input, $vcard->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php b/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php @@ -0,0 +1,50 @@ +createProperty('ORG'); + $elem->setParts($arr); + + $this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $elem->getValue()); + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + + } + + function testGetParts() { + + $str = 'ABC\, Inc.;North American Division;Marketing\;Sales'; + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG'); + $elem->setRawMimeDirValue($str); + + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + } + + function testGetPartsNull() { + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG', null); + + $this->assertEquals(0, count($elem->getParts())); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php b/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php @@ -0,0 +1,30 @@ +parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'}); + + $this->assertEquals(array( + 0.234, + 1.245, + ), $result->{'X-FLOAT'}->getParts()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php @@ -0,0 +1,32 @@ +add('ATTENDEE', $input); + + $this->assertEquals( + $expected, + $property->getNormalizedValue() + ); + + } + + function values() { + + return array( + array('mailto:a@b.com', 'mailto:a@b.com'), + array('mailto:a@b.com', 'MAILTO:a@b.com'), + array('/foo/bar', '/foo/bar'), + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -0,0 +1,359 @@ +vcal = new VCalendar(); + + } + + function testSetDateTime() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetDateTimeLOCAL() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt, $isFloating = true); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeUTC() { + + $tz = new \DateTimeZone('GMT'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000Z', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeLOCALTZ() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeDATE() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem['VALUE'] = 'DATE'; + $elem->setDateTime($dt); + + $this->assertEquals('19850704', (string)$elem); + $this->assertNull($elem['TZID']); + $this->assertEquals('DATE', (string)$elem['VALUE']); + + $this->assertFalse($elem->hasTime()); + } + + function testSetValue() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetValueArray() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetParts() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + function testSetPartsStrings() { + + $dt1 = '19850704T013000Z'; + $dt2 = '19850704T023000Z'; + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + + function testGetDateTimeCached() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals($elem->getDateTime(), $dt); + + } + + function testGetDateTimeDateNULL() { + + $elem = $this->vcal->createProperty('DTSTART'); + $dt = $elem->getDateTime(); + + $this->assertNull($dt); + + } + + function testGetDateTimeDateDATE() { + + $elem = $this->vcal->createProperty('DTSTART','19850704'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateDATEReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART','19850704'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloating() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloatingReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateUTC() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000Z'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('UTC', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeDateLOCALTZ() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = 'Europe/Amsterdam'; + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + /** + * @expectedException LogicException + */ + function testGetDateTimeDateInvalid() { + + $elem = $this->vcal->createProperty('DTSTART','bla'); + $dt = $elem->getDateTime(); + + } + + function testGetDateTimeWeirdTZ() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam'; + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeBadTimeZone() { + + $default = date_default_timezone_get(); + date_default_timezone_set('Canada/Eastern'); + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = 'Moon'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = 'Moon'; + $timezone->{'X-LIC-LOCATION'} = 'Moon'; + + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName()); + date_default_timezone_set($default); + + } + + function testUpdateValueParameter() { + + $dtStart = $this->vcal->createProperty('DTSTART', new \DateTime('2013-06-07 15:05:00')); + $dtStart['VALUE'] = 'DATE'; + + $this->assertEquals("DTSTART;VALUE=DATE:20130607\r\n", $dtStart->serialize()); + + } + + function testValidate() { + + $exDate = $this->vcal->createProperty('EXDATE', '-00011130T143000Z'); + $messages = $exDate->validate(); + $this->assertEquals(1, count($messages)); + $this->assertEquals(3, $messages[0]['level']); + + } + + /** + * This issue was discovered on the sabredav mailing list. + */ + function testCreateDatePropertyThroughAdd() { + + $vcal = new VCalendar(); + $vevent = $vcal->add('VEVENT'); + + $dtstart = $vevent->add( + 'DTSTART', + new \DateTime('2014-03-07'), + array('VALUE' => 'DATE') + ); + + $this->assertEquals("DTSTART;VALUE=DATE:20140307\r\n", $dtstart->serialize()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php @@ -0,0 +1,20 @@ +add('VEVENT', array('DURATION' => array('PT1H'))); + + $this->assertEquals( + new \DateInterval('PT1H'), + $event->{'DURATION'}->getDateInterval() + ); + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php @@ -0,0 +1,46 @@ +add('RRULE', 'FREQ=Daily'); + + $this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur); + + $this->assertEquals(array('FREQ'=>'DAILY'), $recur->getParts()); + $recur->setParts(array('freq'=>'MONTHLY')); + + $this->assertEquals(array('FREQ'=>'MONTHLY'), $recur->getParts()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testSetValueBadVal() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', 'FREQ=Daily'); + $recur->setValue(new \Exception()); + + } + + function testSetSubParts() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', array('FREQ'=>'DAILY', 'BYDAY'=>'mo,tu', 'BYMONTH' => array(0,1))); + + $this->assertEquals(array( + 'FREQ'=>'DAILY', + 'BYDAY' => array('MO','TU'), + 'BYMONTH' => array(0,1), + ), $recur->getParts()); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/TextTest.php b/vendor/sabre/vobject/tests/VObject/Property/TextTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/TextTest.php @@ -0,0 +1,96 @@ +'2.1', + 'PROP' => $propValue + ), false); + + // Adding quoted-printable, because we're testing if it gets removed + // automatically. + $doc->PROP['ENCODING'] = 'QUOTED-PRINTABLE'; + $doc->PROP['P1'] = 'V1'; + + + $output = $doc->serialize(); + + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\n$expected\r\nEND:VCARD\r\n", $output); + + } + + function testSerializeVCard21() { + + $this->assertVCard21Serialization( + 'f;oo', + 'PROP;P1=V1:f;oo' + ); + + } + + function testSerializeVCard21Array() { + + $this->assertVCard21Serialization( + array('f;oo','bar'), + 'PROP;P1=V1:f\;oo;bar' + ); + + } + function testSerializeVCard21Fold() { + + $this->assertVCard21Serialization( + str_repeat('x',80), + 'PROP;P1=V1:' . str_repeat('x',64) . "\r\n " . str_repeat('x',16) + ); + + } + + + + function testSerializeQuotedPrintable() { + + $this->assertVCard21Serialization( + "foo\r\nbar", + 'PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abar' + ); + } + + function testSerializeQuotedPrintableFold() { + + $this->assertVCard21Serialization( + "foo\r\nbarxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abarxxxxxxxxxxxxxxxxxxxxxxxxxx=\r\n xxx" + ); + + } + + function testValidateMinimumPropValue() { + + $vcard = <<assertEquals(1, count($vcard->validate())); + + $this->assertEquals(1, count($vcard->N->getParts())); + + $vcard->validate(\Sabre\VObject\Node::REPAIR); + + $this->assertEquals(5, count($vcard->N->getParts())); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php b/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php @@ -0,0 +1,245 @@ +createProperty('BDAY', $input); + + $this->assertEquals(array($output), $prop->getJsonValue()); + + } + + function dates() { + + return array( + array( + "19961022T140000", + "1996-10-22T14:00:00", + ), + array( + "--1022T1400", + "--10-22T14:00", + ), + array( + "---22T14", + "---22T14", + ), + array( + "19850412", + "1985-04-12", + ), + array( + "1985-04", + "1985-04", + ), + array( + "1985", + "1985", + ), + array( + "--0412", + "--04-12", + ), + array( + "T102200", + "T10:22:00", + ), + array( + "T1022", + "T10:22", + ), + array( + "T10", + "T10", + ), + array( + "T-2200", + "T-22:00", + ), + array( + "T102200Z", + "T10:22:00Z", + ), + array( + "T102200-0800", + "T10:22:00-0800", + ), + array( + "T--00", + "T--00", + ), + ); + + } + + public function testSetParts() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + new \DateTime('2014-04-02 18:37:00') + )); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetPartsTooMany() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + 1, + 2 + )); + + } + + public function testSetPartsString() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + "20140402T183700Z" + )); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + public function testSetValueDateTime() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00') + ); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + public function testSetDateTimeOffset() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')) + ); + + $this->assertEquals('20140402T183700-0400', $prop->getValue()); + + } + + public function testGetDateTime() { + + $datetime = new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime); + + $dt = $prop->getDateTime(); + $this->assertEquals('2014-04-02T18:37:00-04:00', $dt->format('c'), "For some reason this one failed. Current default timezone is: " . date_default_timezone_get()); + + } + + public function testGetDate() { + + $datetime = new \DateTime('2014-04-02'); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime, null, 'DATE'); + + $this->assertEquals('DATE', $prop->getValueType()); + $this->assertEquals('BDAY:20140402', rtrim($prop->serialize())); + + } + + public function testGetDateIncomplete() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + public function testGetDateIncompleteFromVCard() { + + $vcard = <<BDAY; + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + public function testValidate() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals(array(), $prop->validate()); + + } + + public function testValidateBroken() { + + $datetime = '123'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals(array(array( + 'level' => 3, + 'message' => 'The supplied value (123) is not a correct DATE-AND-OR-TIME property', + 'node' => $prop, + )), $prop->validate()); + + } +} + diff --git a/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php b/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php @@ -0,0 +1,48 @@ +parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + + $this->assertEquals('nl', $result->LANG->getValue()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + + function testChangeAndSerialize() { + + $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n"; + $mimeDir = new VObject\Parser\MimeDir($input); + + $result = $mimeDir->parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + // This replicates what the vcard converter does and triggered a bug in + // the past. + $result->LANG->setValue(array('de')); + + $this->assertEquals('de', $result->LANG->getValue()); + + $expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:de\r\nEND:VCARD\r\n"; + $this->assertEquals( + $expected, + $result->serialize() + ); + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/PropertyTest.php b/vendor/sabre/vobject/tests/VObject/PropertyTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/PropertyTest.php @@ -0,0 +1,411 @@ +createProperty('propname','propvalue'); + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('propvalue', $property->__toString()); + $this->assertEquals('propvalue', (string)$property); + $this->assertEquals('propvalue', $property->getValue()); + + } + + function testCreate() { + + $cal = new VCalendar(); + + $params = array( + 'param1' => 'value1', + 'param2' => 'value2', + ); + + $property = $cal->createProperty('propname','propvalue', $params); + + $this->assertEquals('value1', $property['param1']->getValue()); + $this->assertEquals('value2', $property['param2']->getValue()); + + } + + function testSetValue() { + + $cal = new VCalendar(); + + $property = $cal->createProperty('propname','propvalue'); + $property->setValue('value2'); + + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('value2', $property->__toString()); + + } + + function testParameterExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertTrue(isset($property['PARAMNAME'])); + $this->assertTrue(isset($property['paramname'])); + $this->assertFalse(isset($property['foo'])); + + } + + function testParameterGet() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']); + + } + + function testParameterNotExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInternalType('null',$property['foo']); + + } + + function testParameterMultiple() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + $property->add('paramname', 'paramvalue'); + + $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']); + $this->assertEquals(2,count($property['paramname']->getParts())); + + } + + function testSetParameterAsString() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertEquals(1,count($property->parameters())); + $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']); + $this->assertEquals('PARAMNAME',$property->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue',$property->parameters['PARAMNAME']->getValue()); + + } + + function testUnsetParameter() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + unset($property['PARAMNAME']); + $this->assertEquals(0,count($property->parameters())); + + } + + function testSerialize() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + + $this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize()); + + } + + function testSerializeParam() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue', array( + 'paramname' => 'paramvalue', + 'paramname2' => 'paramvalue2', + )); + + $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize()); + + } + + function testSerializeNewLine() { + + $cal = new VCalendar(); + $property = $cal->createProperty('SUMMARY',"line1\nline2"); + + $this->assertEquals("SUMMARY:line1\\nline2\r\n",$property->serialize()); + + } + + function testSerializeLongLine() { + + $cal = new VCalendar(); + $value = str_repeat('!',200); + $property = $cal->createProperty('propname',$value); + + $expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n"; + + $this->assertEquals($expected,$property->serialize()); + + } + + function testSerializeUTF8LineFold() { + + $cal = new VCalendar(); + $value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a + $property = $cal->createProperty('propname', $value); + $expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n"; + $this->assertEquals($expected, $property->serialize()); + + } + + function testGetIterator() { + + $cal = new VCalendar(); + $it = new ElementList(array()); + $property = $cal->createProperty('propname','propvalue'); + $property->setIterator($it); + $this->assertEquals($it,$property->getIterator()); + + } + + + function testGetIteratorDefault() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $it = $property->getIterator(); + $this->assertTrue($it instanceof ElementList); + $this->assertEquals(1,count($it)); + + } + + function testAddScalar() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL'); + + $property->add('myparam','value'); + + $this->assertEquals(1, count($property->parameters())); + + $this->assertTrue($property->parameters['MYPARAM'] instanceof Parameter); + $this->assertEquals('MYPARAM',$property->parameters['MYPARAM']->name); + $this->assertEquals('value',$property->parameters['MYPARAM']->getValue()); + + } + + function testAddParameter() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM','value'); + + $this->assertEquals(1, count($prop->parameters())); + $this->assertEquals('MYPARAM',$prop['myparam']->name); + + } + + function testAddParameterTwice() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM', 'value1'); + $prop->add('MYPARAM', 'value2'); + + $this->assertEquals(1, count($prop->parameters)); + $this->assertEquals(2, count($prop->parameters['MYPARAM']->getParts())); + + $this->assertEquals('MYPARAM',$prop['MYPARAM']->name); + + } + + + function testClone() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL','value'); + $property['FOO'] = 'BAR'; + + $property2 = clone $property; + + $property['FOO'] = 'BAZ'; + $this->assertEquals('BAR', (string)$property2['FOO']); + + } + + function testCreateParams() { + + $cal = new VCalendar(); + $property = $cal->createProperty('X-PROP','value', array( + 'param1' => 'value1', + 'param2' => array('value2', 'value3') + )); + + $this->assertEquals(1, count($property['PARAM1']->getParts())); + $this->assertEquals(2, count($property['PARAM2']->getParts())); + + } + + function testValidateNonUTF8() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', "Bla\x00"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x00)', $result[0]['message']); + $this->assertEquals('Bla', $property->getValue()); + + } + + function testValidateControlChars() { + + $s = "chars["; + foreach (array( + 0x7F, 0x5E, 0x5C, 0x3B, 0x3A, 0x2C, 0x22, 0x20, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + ) as $c) { + $s .= sprintf('%02X(%c)', $c, $c); + } + $s .= "]end"; + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', $s); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x7f)', $result[0]['message']); + $this->assertEquals("chars[7F()5E(^)5C(\\\\)3B(\\;)3A(:)2C(\\,)22(\")20( )1F()1E()1D()1C()1B()1A()19()18()17()16()15()14()13()12()11()10()0F()0E()0D()0C()0B()0A(\\n)09( )08()07()06()05()04()03()02()01()00()]end", $property->getRawMimeDirValue()); + + } + + function testValidateBadPropertyName() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X_*&PROP*", "Bla"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals($result[0]['message'], 'The propertyname: X_*&PROP* contains invalid characters. Only A-Z, 0-9 and - are allowed'); + $this->assertEquals('X-PROP', $property->name); + + } + + function testGetValue() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("SUMMARY", null); + $this->assertEquals(array(), $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue(array()); + $this->assertEquals(array(), $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue(array(1)); + $this->assertEquals(array(1), $property->getParts()); + $this->assertEquals(1, $property->getValue()); + + $property->setValue(array(1,2)); + $this->assertEquals(array(1,2), $property->getParts()); + $this->assertEquals('1,2', $property->getValue()); + + $property->setValue('str'); + $this->assertEquals(array('str'), $property->getParts()); + $this->assertEquals('str', $property->getValue()); + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessSetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + $calendar->{'X-PROP'}[0] = 'Something!'; + + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessUnsetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + unset($calendar->{'X-PROP'}[0]); + + } + + function testValidateBadEncoding() { + + $document = new VCalendar(); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'invalid'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=INVALID is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard4() { + + $document = new VCard(array('VERSION' => '4.0')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING parameter is not valid in vCard 4.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard3() { + + $document = new VCard(array('VERSION' => '3.0')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=BASE64 is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard21() { + + $document = new VCard(array('VERSION' => '2.1')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'B'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/ReaderTest.php b/vendor/sabre/vobject/tests/VObject/ReaderTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/ReaderTest.php @@ -0,0 +1,449 @@ +assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + function testReadStream() { + + $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR"; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + + $result = Reader::read($stream); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + function testReadComponentUnixNewLine() { + + $data = "BEGIN:VCALENDAR\nEND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + function testReadComponentLineFold() { + + $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptComponent() { + + $data = "BEGIN:VCALENDAR\r\nEND:FOO"; + + $result = Reader::read($data); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptSubComponent() { + + $data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR"; + + $result = Reader::read($data); + + } + + function testReadProperty() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + } + + function testReadPropertyWithNewLine() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:Line1\\nLine2\\NLine3\\\\Not the 4th line!\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue()); + + } + + function testReadMappedProperty() { + + $data = "BEGIN:VCALENDAR\r\nDTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + function testReadMappedPropertyGrouped() { + + $data = "BEGIN:VCALENDAR\r\nfoo.DTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadBrokenLine() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue"; + $result = Reader::read($data); + + } + + function testReadPropertyInComponent() { + + $data = array( + "BEGIN:VCALENDAR", + "PROPNAME:propValue", + "END:VCALENDAR" + ); + + $result = Reader::read(implode("\r\n",$data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]); + $this->assertEquals('PROPNAME', $result->children[0]->name); + $this->assertEquals('propValue', $result->children[0]->getValue()); + + } + + function testReadNestedComponent() { + + $data = array( + "BEGIN:VCALENDAR", + "BEGIN:VTIMEZONE", + "BEGIN:DAYLIGHT", + "END:DAYLIGHT", + "END:VTIMEZONE", + "END:VCALENDAR" + ); + + $result = Reader::read(implode("\r\n",$data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]); + $this->assertEquals('VTIMEZONE', $result->children[0]->name); + $this->assertEquals(1, count($result->children[0]->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]); + $this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name); + + + } + + function testReadPropertyParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyRepeatingParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;N=1;N=2;N=3,4;N=\"5\",6;N=\"7,8\";N=9,10;N=^'11^':propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('N', $result->parameters['N']->name); + $this->assertEquals('1,2,3,4,5,6,7,8,9,10,"11"', $result->parameters['N']->getValue()); + $this->assertEquals(array(1,2,3,4,5,6,"7,8",9,10,'"11"'), $result->parameters['N']->getParts()); + + } + + function testReadPropertyRepeatingNamelessGuessedParameter() { + $data = "BEGIN:VCALENDAR\r\nPROPNAME;WORK;VOICE;PREF:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertEquals('WORK,VOICE,PREF', $result->parameters['TYPE']->getValue()); + $this->assertEquals(array('WORK', 'VOICE', 'PREF'), $result->parameters['TYPE']->getParts()); + + } + + function testReadPropertyNoName() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PRODIGY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertTrue($result->parameters['TYPE']->noName); + $this->assertEquals('PRODIGY', $result->parameters['TYPE']); + + } + + function testReadPropertyParameterExtraColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue:anotherrandomstring', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadProperty2Parameters() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(2, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + $this->assertEquals('PARAMNAME2', $result->parameters['PARAMNAME2']->name); + $this->assertEquals('paramvalue2', $result->parameters['PARAMNAME2']->getValue()); + + } + + function testReadPropertyParameterQuoted() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"paramvalue\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterNewLines() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue1^nvalue2^^nvalue3:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->propname; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals("paramvalue1\nvalue2^nvalue3", $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterQuotedColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"param:value\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + $result = $result->propname; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('param:value', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadForgiving() { + + $data = array( + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR" + ); + + $caught = false; + try { + $result = Reader::read(implode("\r\n",$data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n",$data), Reader::OPTION_FORGIVING); + + $expected = implode("\r\n", array( + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR", + "" + )); + + $this->assertEquals($expected, $result->serialize()); + + } + + function testReadWithInvalidLine() { + + $data = array( + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "Yes, we've actually seen a file with non-idented property values on multiple lines", + "END:VCALENDAR" + ); + + $caught = false; + try { + $result = Reader::read(implode("\r\n",$data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n",$data), Reader::OPTION_IGNORE_INVALID_LINES); + + $expected = implode("\r\n", array( + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "END:VCALENDAR", + "" + )); + + $this->assertEquals($expected, $result->serialize()); + + } + + /** + * Reported as Issue 32. + * + * @expectedException \Sabre\VObject\ParseException + */ + public function testReadIncompleteFile() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = array( + "20130929T160000Z", + "20131006T160000Z", + "20131013T160000Z", + "20131020T160000Z", + "20131027T160000Z", + "20140907T160000Z" + ); + + $this->assertEquals($expectedDates, $dates, 'Recursed dates are restricted by month'); + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php @@ -0,0 +1,61 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = array( + "20150101T160000Z", + "20150122T160000Z", + "20150219T160000Z", + "20150319T160000Z", + "20150423T150000Z", + "20150521T150000Z", + "20150618T150000Z", + "20150723T150000Z", + "20150820T150000Z", + "20150917T150000Z", + "20151022T150000Z", + "20151119T160000Z", + "20151224T160000Z", + ); + + $this->assertEquals($expectedDates, $dates); + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php @@ -0,0 +1,122 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31')); + + $result = $vcal->serialize(); + + $output = <<assertEquals($output, str_replace("\r", "", $result)); + + } + + function testExpandWithReferenceTimezone() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'), new \DateTimeZone('Europe/Berlin')); + + $result = $vcal->serialize(); + + $output = <<assertEquals($output, str_replace("\r", "", $result)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php @@ -0,0 +1,54 @@ +VEVENT->UID); + + while($it->valid()) { + $it->next(); + } + + // If we got here, it means we were successful. The bug that was in the + // system before would fail on the 5th tuesday of the month, if the 5th + // tuesday did not exist. + $this->assertTrue(true); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php @@ -0,0 +1,64 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01')); + + $result = $vcal->serialize(); + + $output = <<assertEquals($output, str_replace("\r", "", $result)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php @@ -0,0 +1,99 @@ +vcal = new VCalendar(); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastForwardTooFar() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'foobar'; + $ev->DTSTART = '20090420T180000Z'; + $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1'; + + $this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00'))); + + } + + /** + * Different bug, also likely an infinite loop. + */ + function testYearlyByMonthLoop() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120101T154500'; + $ev->DTSTART['TZID'] = 'Europe/Berlin'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA'; + $ev->DTEND = '20120101T164500'; + $ev->DTEND['TZID'] = 'Europe/Berlin'; + + // This recurrence rule by itself is a yearly rule that should happen + // every february. + // + // The BYDAY part expands this to every day of the month, but the + // BYSETPOS limits this to only the 1st day of the month. Very crazy + // way to specify this, and could have certainly been a lot easier. + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal,'uuid'); + $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC'))); + + $collect = array(); + + while($it->valid()) { + $collect[] = $it->getDTSTART(); + if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) { + break; + } + $it->next(); + + } + + $this->assertEquals( + array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))), + $collect + ); + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException InvalidArgumentException + * @return void + */ + function testZeroInterval() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120824T145700Z'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=0'; + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal,'uuid'); + $it->fastForward(new DateTime('2013-01-01 23:00:00', new DateTimeZone('UTC'))); + + // if we got this far.. it means we are no longer infinitely looping + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php @@ -0,0 +1,49 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, 'foo'); + + $result = iterator_to_array($it); + + $tz = new DateTimeZone('Europe/Moscow'); + + $expected = array( + new DateTime('2013-07-10 11:00:00', $tz), + new DateTime('2013-07-12 11:00:00', $tz), + new DateTime('2013-07-13 11:00:00', $tz), + ); + + $this->assertEquals($expected, $result); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php @@ -0,0 +1,128 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724'); + + $result = array(); + foreach($it as $instance) { + + $result[] = $instance; + + } + + $tz = new DateTimeZone('Europe/Brussels'); + + $this->assertEquals(array( + new DateTime('2013-07-15 09:00:00', $tz), + new DateTime('2013-07-16 07:00:00', $tz), + new DateTime('2013-07-17 07:00:00', $tz), + new DateTime('2013-07-18 09:00:00', $tz), + new DateTime('2013-07-19 07:00:00', $tz), + ), $result); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php @@ -0,0 +1,1427 @@ +createComponent('VEVENT'); + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07')); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $this->assertTrue($it->isInfinite()); + + } + + /** + * @expectedException InvalidArgumentException + * @depends testValues + */ + function testInvalidFreq() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $ev->UID = 'foo'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarNoUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarInvalidUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal,'foo'); + + } + + /** + * @depends testValues + */ + function testHourly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 12:00:00', $tz), + new DateTime('2011-10-07 15:00:00', $tz), + new DateTime('2011-10-07 18:00:00', $tz), + new DateTime('2011-10-07 21:00:00', $tz), + new DateTime('2011-10-08 00:00:00', $tz), + new DateTime('2011-10-08 03:00:00', $tz), + new DateTime('2011-10-08 06:00:00', $tz), + new DateTime('2011-10-08 09:00:00', $tz), + new DateTime('2011-10-08 12:00:00', $tz), + new DateTime('2011-10-08 15:00:00', $tz), + new DateTime('2011-10-08 18:00:00', $tz), + new DateTime('2011-10-08 21:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDaily() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-10', $tz), + new DateTime('2011-10-13', $tz), + new DateTime('2011-10-16', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-22', $tz), + new DateTime('2011-10-25', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testNoRRULE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-08 06:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new datetime('2011-10-08 06:00:00', $tz), + new datetime('2011-10-08 07:00:00', $tz), + new datetime('2011-10-09 06:00:00', $tz), + new datetime('2011-10-09 07:00:00', $tz), + new datetime('2011-10-15 06:00:00', $tz), + new datetime('2011-10-15 07:00:00', $tz), + new datetime('2011-10-16 06:00:00', $tz), + new datetime('2011-10-16 07:00:00', $tz), + new datetime('2011-10-22 06:00:00', $tz), + new datetime('2011-10-22 07:00:00', $tz), + new datetime('2011-10-23 06:00:00', $tz), + new datetime('2011-10-23 07:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2012-10-11 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new datetime('2012-10-11 12:00:00', $tz), + new datetime('2012-10-11 13:00:00', $tz), + new datetime('2012-10-11 14:00:00', $tz), + new datetime('2012-10-11 15:00:00', $tz), + new datetime('2012-10-13 10:00:00', $tz), + new datetime('2012-10-13 11:00:00', $tz), + new datetime('2012-10-13 12:00:00', $tz), + new datetime('2012-10-13 13:00:00', $tz), + new datetime('2012-10-13 14:00:00', $tz), + new datetime('2012-10-13 15:00:00', $tz), + new datetime('2012-10-15 10:00:00', $tz), + new datetime('2012-10-15 11:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-11', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-10-25', $tz), + new DateTime('2011-11-02', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-08', $tz), + new DateTime('2011-11-16', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-11-22', $tz), + new DateTime('2011-11-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeekly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-12-02', $tz), + new DateTime('2011-12-16', $tz), + new DateTime('2011-12-30', $tz), + new DateTime('2012-01-13', $tz), + new DateTime('2012-01-27', $tz), + new DateTime('2012-02-10', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 08:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 15; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 08:00:00', $tz), + new DateTime('2011-10-07 09:00:00', $tz), + new DateTime('2011-10-07 10:00:00', $tz), + new DateTime('2011-10-18 08:00:00', $tz), + new DateTime('2011-10-18 09:00:00', $tz), + new DateTime('2011-10-18 10:00:00', $tz), + new DateTime('2011-10-19 08:00:00', $tz), + new DateTime('2011-10-19 09:00:00', $tz), + new DateTime('2011-10-19 10:00:00', $tz), + new DateTime('2011-10-21 08:00:00', $tz), + new DateTime('2011-10-21 09:00:00', $tz), + new DateTime('2011-10-21 10:00:00', $tz), + new DateTime('2011-11-01 08:00:00', $tz), + new DateTime('2011-11-01 09:00:00', $tz), + new DateTime('2011-11-01 10:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDaySpecificHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 18:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 18:00:00', $tz), + new DateTime('2011-10-18 18:00:00', $tz), + new DateTime('2011-10-19 18:00:00', $tz), + new DateTime('2011-10-21 18:00:00', $tz), + new DateTime('2011-11-01 18:00:00', $tz), + new DateTime('2011-11-02 18:00:00', $tz), + new DateTime('2011-11-04 18:00:00', $tz), + new DateTime('2011-11-15 18:00:00', $tz), + new DateTime('2011-11-16 18:00:00', $tz), + new DateTime('2011-11-18 18:00:00', $tz), + new DateTime('2011-11-29 18:00:00', $tz), + new DateTime('2011-11-30 18:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-18', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-11-01', $tz), + new DateTime('2011-11-02', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-15', $tz), + new DateTime('2011-11-16', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-11-29', $tz), + new DateTime('2011-11-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-12-05', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-12-05', $tz), + new DateTime('2012-03-05', $tz), + new DateTime('2012-06-05', $tz), + new DateTime('2012-09-05', $tz), + new DateTime('2012-12-05', $tz), + ), + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyEndOfMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-12-31', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-12-31', $tz), + new DateTime('2012-08-31', $tz), + new DateTime('2012-10-31', $tz), + new DateTime('2012-12-31', $tz), + new DateTime('2013-08-31', $tz), + new DateTime('2013-10-31', $tz), + new DateTime('2013-12-31', $tz), + new DateTime('2014-08-31', $tz), + new DateTime('2014-10-31', $tz), + new DateTime('2014-12-31', $tz), + new DateTime('2015-08-31', $tz), + new DateTime('2015-10-31', $tz), + ), + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-01', $tz), + new DateTime('2011-01-25', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-06-01', $tz), + new DateTime('2011-06-24', $tz), + new DateTime('2011-11-01', $tz), + new DateTime('2011-11-24', $tz), + new DateTime('2012-04-01', $tz), + new DateTime('2012-04-24', $tz), + ), + $result + ); + + } + + /** + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @depends testValues + * @medium + */ + function testMonthlyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-03', $tz), + new DateTime('2011-01-05', $tz), + new DateTime('2011-01-10', $tz), + new DateTime('2011-01-17', $tz), + new DateTime('2011-01-18', $tz), + new DateTime('2011-01-20', $tz), + new DateTime('2011-01-24', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-03-02', $tz), + new DateTime('2011-03-07', $tz), + new DateTime('2011-03-14', $tz), + new DateTime('2011-03-17', $tz), + new DateTime('2011-03-21', $tz), + new DateTime('2011-03-22', $tz), + new DateTime('2011-03-28', $tz), + new DateTime('2011-05-02', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-08-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-08-01', $tz), + new DateTime('2012-10-01', $tz), + new DateTime('2013-04-01', $tz), + new DateTime('2013-07-01', $tz), + new DateTime('2014-09-01', $tz), + new DateTime('2014-12-01', $tz), + new DateTime('2015-06-01', $tz), + new DateTime('2016-02-01', $tz), + new DateTime('2016-08-01', $tz), + new DateTime('2017-05-01', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayBySetPos() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-03', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-02-01', $tz), + new DateTime('2011-02-28', $tz), + new DateTime('2011-03-01', $tz), + new DateTime('2011-03-31', $tz), + new DateTime('2011-04-01', $tz), + new DateTime('2011-04-29', $tz), + new DateTime('2011-05-02', $tz), + new DateTime('2011-05-31', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-01', $tz), + new DateTime('2014-01-01', $tz), + new DateTime('2017-01-01', $tz), + new DateTime('2020-01-01', $tz), + new DateTime('2023-01-01', $tz), + new DateTime('2026-01-01', $tz), + new DateTime('2029-01-01', $tz), + new DateTime('2032-01-01', $tz), + new DateTime('2035-01-01', $tz), + new DateTime('2038-01-01', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyLeapYear() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2012-02-29', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2012-02-29', $tz), + new DateTime('2016-02-29', $tz), + new DateTime('2020-02-29', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-04-07', $tz), + new DateTime('2011-10-07', $tz), + new DateTime('2015-04-07', $tz), + new DateTime('2015-10-07', $tz), + new DateTime('2019-04-07', $tz), + new DateTime('2019-10-07', $tz), + new DateTime('2023-04-07', $tz), + new DateTime('2023-10-07', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonthByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-04-04', $tz), + new DateTime('2011-04-24', $tz), + new DateTime('2011-10-03', $tz), + new DateTime('2011-10-30', $tz), + new DateTime('2016-04-04', $tz), + new DateTime('2016-04-24', $tz), + new DateTime('2016-10-03', $tz), + new DateTime('2016-10-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testFastForward() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $it->fastForward(new DateTime('2020-05-05', new DateTimeZone('UTC'))); + + $max = 20; + $result = array(); + while($item = $it->current()) { + + $result[] = $item; + $max--; + + if (!$max) break; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array(), $result); + + } + + /** + * @depends testValues + */ + function testComplexExclusions() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + + $tz = new DateTimeZone('Canada/Eastern'); + $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz)); + + $exDate1 = $vcal->createProperty('EXDATE'); + $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz))); + $exDate2 = $vcal->createProperty('EXDATE'); + $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz))); + + $ev->add($dtStart); + $ev->add($exDate1); + $ev->add($exDate2); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $this->assertEquals( + array( + new DateTime('2011-01-01 13:50:20', $tz), + new DateTime('2013-01-01 13:50:20', $tz), + new DateTime('2015-01-01 13:50:20', $tz), + new DateTime('2017-01-01 13:50:20', $tz), + new DateTime('2018-01-01 13:50:20', $tz), + new DateTime('2019-01-01 13:50:20', $tz), + new DateTime('2020-01-01 13:50:20', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testOverridenEvent() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=DAILY;COUNT=10'; + $ev1->DTSTART = '20120107T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + while($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array( + new DateTime('2012-01-07 12:00:00',$tz), + new DateTime('2012-01-08 12:00:00',$tz), + new DateTime('2012-01-09 12:00:00',$tz), + new DateTime('2012-01-10 14:00:00',$tz), + new DateTime('2012-01-11 12:00:00',$tz), + new DateTime('2012-01-12 12:00:00',$tz), + new DateTime('2012-01-14 12:00:00',$tz), + new DateTime('2012-01-15 12:00:00',$tz), + new DateTime('2012-01-15 14:00:00',$tz), + new DateTime('2012-01-16 12:00:00',$tz), + ), $dates); + + $this->assertEquals(array( + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 2', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 3', + 'baseEvent', + ), $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEvent2() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120112T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120119T120000Z'; + $ev2->DTSTART = '20120113T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + while($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array( + new DateTime('2012-01-12 12:00:00',$tz), + new DateTime('2012-01-13 12:00:00',$tz), + new DateTime('2012-01-26 12:00:00',$tz), + + ), $dates); + + $this->assertEquals(array( + 'baseEvent', + 'Override!', + 'baseEvent', + ), $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEventNoValuesExpected() { + + $vcal = new VCalendar(); + $ev1 = $vcal->createComponent('VEVENT'); + + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120124T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120131T120000Z'; + $ev2->DTSTART = '20120125T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + + // The reported problem was specifically related to the VCALENDAR + // expansion. In this parcitular case, we had to forward to the 28th of + // january. + $it->fastForward(new DateTime('2012-01-28 23:00:00')); + + // We stop the loop when it hits the 6th of februari. Normally this + // iterator would hit 24, 25 (overriden from 31) and 7 feb but because + // we 'filter' from the 28th till the 6th, we should get 0 results. + while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $this->assertEquals(array(), $dates); + $this->assertEquals(array(), $summaries); + + } + + /** + * @depends testValues + */ + function testRDATE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RDATE = array( + new DateTime('2014-08-07', new DateTimeZone('UTC')), + new DateTime('2014-08-08', new DateTimeZone('UTC')), + ); + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2014-08-07', $tz), + new DateTime('2014-08-08', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + * @expectedException \InvalidArgumentException + */ + function testNoMasterBadUID() { + + $vcal = new VCalendar(); + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal,'broken'); + + } +} + diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php @@ -0,0 +1,65 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01')); + + $result = $vcal->serialize(); + + $output = <<assertEquals($output, str_replace("\r","",$result)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php @@ -0,0 +1,40 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new EventIterator($vcal, 'foo'); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php @@ -0,0 +1,122 @@ +expand(new DateTime('2014-08-01'), new DateTime('2014-09-01')); + + $expected = <<serialize(); + $newIcs = str_replace("\r\n","\n", $newIcs); + $this->assertEquals( + $expected, + $newIcs + ); + + + } + + function testRemoveFirstEvent() { + + $input = <<expand(new DateTime('2014-08-01'), new DateTime('2014-08-19')); + + $expected = <<serialize(); + $newIcs = str_replace("\r\n","\n", $newIcs); + $this->assertEquals( + $expected, + $newIcs + ); + + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php b/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php @@ -0,0 +1,56 @@ +assertEquals( + $expected, + iterator_to_array($it) + ); + + $this->assertFalse($it->isInfinite()); + + } + + function testFastForward() { + + $utc = new DateTimeZone('UTC'); + $it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc)); + + $it->fastForward(new DateTime('2014-08-15 00:00:00')); + + $result = array(); + while($it->valid()) { + $result[] = $it->current(); + $it->next(); + } + + $expected = array( + new DateTime('2014-09-01 00:00:00', $utc), + new DateTime('2014-10-01 00:00:00', $utc), + ); + + $this->assertEquals( + $expected, + $result + ); + + $this->assertFalse($it->isInfinite()); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php b/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php @@ -0,0 +1,713 @@ +parse( + 'FREQ=HOURLY;INTERVAL=3;COUNT=12', + '2011-10-07 12:00:00', + array( + '2011-10-07 12:00:00', + '2011-10-07 15:00:00', + '2011-10-07 18:00:00', + '2011-10-07 21:00:00', + '2011-10-08 00:00:00', + '2011-10-08 03:00:00', + '2011-10-08 06:00:00', + '2011-10-08 09:00:00', + '2011-10-08 12:00:00', + '2011-10-08 15:00:00', + '2011-10-08 18:00:00', + '2011-10-08 21:00:00', + ) + ); + + } + + function testDaily() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + array( + '2011-10-07 00:00:00', + '2011-10-10 00:00:00', + '2011-10-13 00:00:00', + '2011-10-16 00:00:00', + '2011-10-19 00:00:00', + '2011-10-22 00:00:00', + '2011-10-25 00:00:00', + ) + ); + + } + + function testDailyByDayByHour() { + + $this->parse( + 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7', + '2011-10-08 06:00:00', + array( + '2011-10-08 06:00:00', + '2011-10-08 07:00:00', + '2011-10-09 06:00:00', + '2011-10-09 07:00:00', + '2011-10-15 06:00:00', + '2011-10-15 07:00:00', + '2011-10-16 06:00:00', + '2011-10-16 07:00:00', + '2011-10-22 06:00:00', + '2011-10-22 07:00:00', + '2011-10-23 06:00:00', + '2011-10-23 07:00:00', + ) + ); + + } + + function testDailyByHour() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15', + '2012-10-11 12:00:00', + array( + '2012-10-11 12:00:00', + '2012-10-11 13:00:00', + '2012-10-11 14:00:00', + '2012-10-11 15:00:00', + '2012-10-13 10:00:00', + '2012-10-13 11:00:00', + '2012-10-13 12:00:00', + '2012-10-13 13:00:00', + '2012-10-13 14:00:00', + '2012-10-13 15:00:00', + '2012-10-15 10:00:00', + '2012-10-15 11:00:00', + ) + ); + + } + + function testDailyByDay() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR', + '2011-10-07 12:00:00', + array( + '2011-10-07 12:00:00', + '2011-10-11 12:00:00', + '2011-10-19 12:00:00', + '2011-10-21 12:00:00', + '2011-10-25 12:00:00', + '2011-11-02 12:00:00', + '2011-11-04 12:00:00', + '2011-11-08 12:00:00', + '2011-11-16 12:00:00', + '2011-11-18 12:00:00', + '2011-11-22 12:00:00', + '2011-11-30 12:00:00', + ) + ); + + } + + function testDailyCount() { + + $this->parse( + 'FREQ=DAILY;COUNT=5', + '2014-08-01 18:03:00', + array( + '2014-08-01 18:03:00', + '2014-08-02 18:03:00', + '2014-08-03 18:03:00', + '2014-08-04 18:03:00', + '2014-08-05 18:03:00', + ) + ); + + } + + function testDailyByMonth() { + + $this->parse( + 'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU', + '2007-10-04 16:00:00', + array( + "2013-09-29 16:00:00", + "2013-10-06 16:00:00", + "2013-10-13 16:00:00", + "2013-10-20 16:00:00", + "2013-10-27 16:00:00", + "2014-09-07 16:00:00" + ), + '2013-09-28' + ); + + } + + function testWeekly() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;COUNT=10', + '2011-10-07 00:00:00', + array( + '2011-10-07 00:00:00', + '2011-10-21 00:00:00', + '2011-11-04 00:00:00', + '2011-11-18 00:00:00', + '2011-12-02 00:00:00', + '2011-12-16 00:00:00', + '2011-12-30 00:00:00', + '2012-01-13 00:00:00', + '2012-01-27 00:00:00', + '2012-02-10 00:00:00', + ) + ); + + } + + function testWeeklyByDay() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA', + '2014-08-01 00:00:00', + array( + '2014-08-01 00:00:00', + '2014-08-04 00:00:00', + '2014-08-11 00:00:00', + '2014-08-18 00:00:00', + ) + ); + + } + + function testWeeklyByDay2() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 00:00:00', + array( + '2011-10-07 00:00:00', + '2011-10-18 00:00:00', + '2011-10-19 00:00:00', + '2011-10-21 00:00:00', + '2011-11-01 00:00:00', + '2011-11-02 00:00:00', + '2011-11-04 00:00:00', + '2011-11-15 00:00:00', + '2011-11-16 00:00:00', + '2011-11-18 00:00:00', + '2011-11-29 00:00:00', + '2011-11-30 00:00:00', + ) + ); + + } + + function testWeeklyByDayByHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10', + '2011-10-07 08:00:00', + array( + '2011-10-07 08:00:00', + '2011-10-07 09:00:00', + '2011-10-07 10:00:00', + '2011-10-18 08:00:00', + '2011-10-18 09:00:00', + '2011-10-18 10:00:00', + '2011-10-19 08:00:00', + '2011-10-19 09:00:00', + '2011-10-19 10:00:00', + '2011-10-21 08:00:00', + '2011-10-21 09:00:00', + '2011-10-21 10:00:00', + '2011-11-01 08:00:00', + '2011-11-01 09:00:00', + '2011-11-01 10:00:00', + ) + ); + + } + + function testWeeklyByDaySpecificHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 18:00:00', + array( + '2011-10-07 18:00:00', + '2011-10-18 18:00:00', + '2011-10-19 18:00:00', + '2011-10-21 18:00:00', + '2011-11-01 18:00:00', + '2011-11-02 18:00:00', + '2011-11-04 18:00:00', + '2011-11-15 18:00:00', + '2011-11-16 18:00:00', + '2011-11-18 18:00:00', + '2011-11-29 18:00:00', + '2011-11-30 18:00:00', + ) + ); + + } + + function testMonthly() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=3;COUNT=5', + '2011-12-05 00:00:00', + array( + '2011-12-05 00:00:00', + '2012-03-05 00:00:00', + '2012-06-05 00:00:00', + '2012-09-05 00:00:00', + '2012-12-05 00:00:00', + ) + ); + + } + + function testMonlthyEndOfMonth() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=12', + '2011-12-31 00:00:00', + array( + '2011-12-31 00:00:00', + '2012-08-31 00:00:00', + '2012-10-31 00:00:00', + '2012-12-31 00:00:00', + '2013-08-31 00:00:00', + '2013-10-31 00:00:00', + '2013-12-31 00:00:00', + '2014-08-31 00:00:00', + '2014-10-31 00:00:00', + '2014-12-31 00:00:00', + '2015-08-31 00:00:00', + '2015-10-31 00:00:00', + ) + ); + + } + + function testMonthlyByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7', + '2011-01-01 00:00:00', + array( + '2011-01-01 00:00:00', + '2011-01-25 00:00:00', + '2011-01-31 00:00:00', + '2011-06-01 00:00:00', + '2011-06-24 00:00:00', + '2011-11-01 00:00:00', + '2011-11-24 00:00:00', + '2012-04-01 00:00:00', + '2012-04-24 00:00:00', + ) + ); + + } + + function testMonthlyByDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH', + '2011-01-03 00:00:00', + array( + '2011-01-03 00:00:00', + '2011-01-05 00:00:00', + '2011-01-10 00:00:00', + '2011-01-17 00:00:00', + '2011-01-18 00:00:00', + '2011-01-20 00:00:00', + '2011-01-24 00:00:00', + '2011-01-31 00:00:00', + '2011-03-02 00:00:00', + '2011-03-07 00:00:00', + '2011-03-14 00:00:00', + '2011-03-17 00:00:00', + '2011-03-21 00:00:00', + '2011-03-22 00:00:00', + '2011-03-28 00:00:00', + '2011-05-02 00:00:00', + ) + ); + + } + + function testMonthlyByDayByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1', + '2011-08-01 00:00:00', + array( + '2011-08-01 00:00:00', + '2012-10-01 00:00:00', + '2013-04-01 00:00:00', + '2013-07-01 00:00:00', + '2014-09-01 00:00:00', + '2014-12-01 00:00:00', + '2015-06-01 00:00:00', + '2016-02-01 00:00:00', + '2016-08-01 00:00:00', + '2017-05-01 00:00:00', + ) + ); + + } + + function testMonthlyByDayBySetPos() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1', + '2011-01-03 00:00:00', + array( + '2011-01-03 00:00:00', + '2011-01-31 00:00:00', + '2011-02-01 00:00:00', + '2011-02-28 00:00:00', + '2011-03-01 00:00:00', + '2011-03-31 00:00:00', + '2011-04-01 00:00:00', + '2011-04-29 00:00:00', + '2011-05-02 00:00:00', + '2011-05-31 00:00:00', + ) + ); + + } + + function testYearly() { + + $this->parse( + 'FREQ=YEARLY;COUNT=10;INTERVAL=3', + '2011-01-01 00:00:00', + array( + '2011-01-01 00:00:00', + '2014-01-01 00:00:00', + '2017-01-01 00:00:00', + '2020-01-01 00:00:00', + '2023-01-01 00:00:00', + '2026-01-01 00:00:00', + '2029-01-01 00:00:00', + '2032-01-01 00:00:00', + '2035-01-01 00:00:00', + '2038-01-01 00:00:00', + ) + ); + } + + function testYearlyLeapYear() { + + $this->parse( + 'FREQ=YEARLY;COUNT=3', + '2012-02-29 00:00:00', + array( + '2012-02-29 00:00:00', + '2016-02-29 00:00:00', + '2020-02-29 00:00:00', + ) + ); + } + + function testYearlyByMonth() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10', + '2011-04-07 00:00:00', + array( + '2011-04-07 00:00:00', + '2011-10-07 00:00:00', + '2015-04-07 00:00:00', + '2015-10-07 00:00:00', + '2019-04-07 00:00:00', + '2019-10-07 00:00:00', + '2023-04-07 00:00:00', + '2023-10-07 00:00:00', + ) + ); + + } + + function testYearlyByMonthByDay() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + array( + '2011-04-04 00:00:00', + '2011-04-24 00:00:00', + '2011-10-03 00:00:00', + '2011-10-30 00:00:00', + '2016-04-04 00:00:00', + '2016-04-24 00:00:00', + '2016-10-03 00:00:00', + '2016-10-30 00:00:00', + ) + ); + + } + + function testFastForward() { + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + array(), + '2020-05-05 00:00:00' + ); + + } + + /** + * The bug that was in the + * system before would fail on the 5th tuesday of the month, if the 5th + * tuesday did not exist. + * + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @medium + */ + function testFifthTuesdayProblem() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU', + '2007-10-04 14:46:42', + array( + "2007-10-04 14:46:42", + ) + ); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastFowardTooFar() { + + $this->parse( + 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1', + '2009-04-20 18:00:00', + array( + '2009-04-20 18:00:00', + '2009-04-27 18:00:00', + '2009-05-04 18:00:00', + '2009-05-11 18:00:00', + '2009-05-18 18:00:00', + '2009-05-25 18:00:00', + '2009-06-01 18:00:00', + '2009-06-08 18:00:00', + '2009-06-15 18:00:00', + '2009-06-22 18:00:00', + '2009-06-29 18:00:00', + ) + ); + + } + + /** + * This also at one point caused an infinite loop. We're keeping the test. + */ + function testYearlyByMonthLoop() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA', + '2012-01-01 15:45:00', + array( + '2012-02-01 15:45:00', + ), + '2012-01-29 23:00:00' + ); + + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException InvalidArgumentException + */ + function testZeroInterval() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=0', + '2012-08-24 14:57:00', + array(), + '2013-01-01 23:00:00' + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidFreq() { + + $this->parse( + 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + array() + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testByDayBadOffset() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA', + '2014-08-01 00:00:00', + array() + ); + + } + + function testUntilBeginHAsTimezone() { + + $this->parse( + 'FREQ=WEEKLY;UNTIL=20131118T183000', + '2013-09-23 18:30:00', + array( + '2013-09-23 18:30:00', + '2013-09-30 18:30:00', + '2013-10-07 18:30:00', + '2013-10-14 18:30:00', + '2013-10-21 18:30:00', + '2013-10-28 18:30:00', + '2013-11-04 18:30:00', + '2013-11-11 18:30:00', + '2013-11-18 18:30:00', + ), + null, + 'America/New_York' + ); + + } + + function testUntilBeforeDtStart() { + + $this->parse( + 'FREQ=DAILY;UNTIL=20140101T000000Z', + '2014-08-02 00:15:00', + array( + '2014-08-02 00:15:00', + ) + ); + + } + + function testIgnoredStuff() { + + $this->parse( + 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2', + '2014-08-02 00:15:00', + array( + '2014-08-02 00:15:00', + '2014-08-03 00:15:00', + ) + ); + + } + + function testMinusFifthThursday() { + + $this->parse( + 'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4', + '2015-01-01 00:15:00', + array( + '2015-01-01 00:15:00', + '2015-01-08 00:15:00', + '2015-02-05 00:15:00', + '2015-03-05 00:15:00' + ) + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnsupportedPart() { + + $this->parse( + 'FREQ=DAILY;BYWODAN=1', + '2014-08-02 00:15:00', + array() + ); + + } + + function testIteratorFunctions() { + + $parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13')); + $parser->next(); + $this->assertEquals( + new DateTime('2014-08-03 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 1, + $parser->key() + ); + + $parser->rewind(); + + $this->assertEquals( + new DateTime('2014-08-02 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 0, + $parser->key() + ); + + } + + function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') { + + $dt = new DateTime($start, new DateTimeZone($tz)); + $parser = new RRuleIterator($rule, $dt); + + if ($fastForward) { + $parser->fastForward(new DateTime($fastForward)); + } + + $result = array(); + while($parser->valid()) { + + $item = $parser->current(); + $result[] = $item->format('Y-m-d H:i:s'); + + if ($parser->isInfinite() && count($result) >= count($expected)) { + break; + } + $parser->next(); + + } + + $this->assertEquals( + $expected, + $result + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics b/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-TIMEZONE:America/New_York +PRODID:-//www.churchcommunitybuilder.com//Church Community Builder//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Test Event +BEGIN:VTIMEZONE +TZID:America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:10621-1440@ccbchurch.com +DTSTART;TZID=America/New_York:20130923T183000 +DTEND;TZID=America/New_York:20130923T203000 +DTSTAMP:20131216T170211 +RRULE:FREQ=WEEKLY;UNTIL=20131118T183000 +CREATED:20130423T161111 +DESCRIPTION:Test Event ending November 11, 2013 +LAST-MODIFIED:20131126T163428 +SEQUENCE:1387231331 +SUMMARY:Test +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/vendor/sabre/vobject/tests/VObject/SlashRTest.php b/vendor/sabre/vobject/tests/VObject/SlashRTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/SlashRTest.php @@ -0,0 +1,20 @@ +add('test', "abc\r\ndef"); + $this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize()); + + } + + +} diff --git a/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php b/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php @@ -0,0 +1,325 @@ +version = VObject\Version::VERSION; + } + + function createStream($data) { + + $stream = fopen('php://memory','r+'); + fwrite($stream, $data); + rewind($stream); + return $stream; + + } + + function testICalendarImportValidEvent() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportWrongType() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportEndOfData() { + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertNull($object=$objects->getNext()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportInvalidEvent() { + $data = <<createStream($data); + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportMultipleValidEvents() { + + $event[] = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while($object=$objects->getNext()) { + + $expected = <<version//EN +CALSCALE:GREGORIAN +$event[$i] +END:VCALENDAR + +EOT; + + $return .= $object->serialize(); + $expected = str_replace("\n", "\r\n", $expected); + $this->assertEquals($expected, $object->serialize()); + $i++; + } + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + function testICalendarImportEventWithoutUID() { + + $data = <<version//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART:20140101T040000Z +DTSTAMP:20140122T233226Z +END:VEVENT +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + + if ($messages) { + $messages = array_map( + function($item) { return $item['message']; }, + $messages + ); + $this->fail('Validation errors: ' . implode("\n", $messages)); + } else { + $this->assertEquals(array(), $messages); + } + } + + function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents() { + + $timezones = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while($object=$objects->getNext()) { + + $expected = <<version//EN +CALSCALE:GREGORIAN +$timezones +$event[$i] +END:VCALENDAR + +EOT; + $expected = str_replace("\n", "\r\n", $expected); + + $this->assertEquals($expected, $object->serialize()); + $return .= $object->serialize(); + $i++; + + } + + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + function testICalendarImportWithOutVTIMEZONES() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + $this->assertEquals(array(), $messages); + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php b/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php @@ -0,0 +1,195 @@ +createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + $this->assertEquals(1, $count); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testVCardImportWrongType() { + $event[] = <<createStream($data); + + $splitter = new VCard($tempFile); + + while($object=$splitter->getNext()) { + } + + } + + function testVCardImportValidVCardsWithCategories() { + $data = <<createStream($data); + + $splitter = new VCard($tempFile); + + $count = 0; + while($object=$splitter->getNext()) { + $count++; + } + $this->assertEquals(4, $count); + + } + + function testVCardImportEndOfData() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + $object=$objects->getNext(); + + $this->assertNull($objects->getNext()); + + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testVCardImportCheckInvalidArgumentException() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + while($objects->getNext()) { } + + } + + function testVCardImportMultipleValidVCards() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + + } + + function testImportMultipleSeparatedWithNewLines() { + $data = <<createStream($data); + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + } + + function testVCardImportVCardWithoutUID() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + + $this->assertEquals(1, $count); + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/StringUtilTest.php b/vendor/sabre/vobject/tests/VObject/StringUtilTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/StringUtilTest.php @@ -0,0 +1,55 @@ +assertEquals(false, $string); + + } + + function testIsUTF8() { + + $string = StringUtil::isUTF8('I 💚 SabreDAV'); + + $this->assertEquals(true, $string); + + } + + function testUTF8ControlChar() { + + $string = StringUtil::isUTF8(chr(0x00)); + + $this->assertEquals(false, $string); + + } + + function testConvertToUTF8nonUTF8() { + + $string = StringUtil::convertToUTF8(chr(0xbf)); + + $this->assertEquals(utf8_encode(chr(0xbf)), $string); + + } + + function testConvertToUTF8IsUTF8() { + + $string = StringUtil::convertToUTF8('I 💚 SabreDAV'); + + $this->assertEquals('I 💚 SabreDAV', $string); + + } + + function testConvertToUTF8ControlChar() { + + $string = StringUtil::convertToUTF8(chr(0x00)); + + $this->assertEquals('', $string); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/TestCase.php b/vendor/sabre/vobject/tests/VObject/TestCase.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/TestCase.php @@ -0,0 +1,50 @@ +fail('Input must be a string, stream or VObject component'); + } + unset($input->PRODID); + return $input; + + }; + + $expected = $getObj($expected); + $actual = $getObj($actual); + + $this->assertEquals( + $expected->serialize(), + $actual->serialize(), + $message + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php b/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php @@ -0,0 +1,369 @@ +assertInstanceOf('DateTimeZone', $tz); + } catch (\Exception $e) { + if (strpos($e->getMessage(), "Unknown or bad timezone")!==false) { + $this->markTestSkipped($timezoneName . ' is not (yet) supported in this PHP version. Update pecl/timezonedb'); + } else { + throw $e; + } + + } + + } + + function getMapping() { + + TimeZoneUtil::loadTzMaps(); + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + TimeZoneUtil::$map + ); + + } + + function testExchangeMap() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWetherMicrosoftIsStillInsane() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testUnknownExchangeId() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWindowsTimeZone() { + + $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time'); + $ex = new \DateTimeZone('America/New_York'); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneIdentifiers + */ + function testTimeZoneIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneBCIdentifiers + */ + function testTimeZoneBCIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function getPHPTimeZoneIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + \DateTimeZone::listIdentifiers() + ); + + } + + function getPHPTimeZoneBCIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + TimeZoneUtil::getIdentifiersBC() + ); + + } + + function testTimezoneOffset() { + + $tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true); + + if (version_compare(PHP_VERSION, '5.5.10', '>=') && !defined('HHVM_VERSION')) { + $ex = new \DateTimeZone('-04:00'); + } else { + $ex = new \DateTimeZone('Etc/GMT-4'); + } + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testTimezoneFail() { + + $tz = TimeZoneUtil::getTimeZone('FooBar', null, true); + + } + + function testFallBack() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testLjubljanaBug() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWeirdSystemVLICs() { + +$vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php b/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php @@ -0,0 +1,37 @@ +assertTrue( + UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555') + ); + $this->assertTrue( + UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555') + ); + + } + + /** + * @depends testValidateUUID + */ + function testGetUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID( + UUIDUtil::getUUID() + ) + ); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/VCard21Test.php b/vendor/sabre/vobject/tests/VObject/VCard21Test.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/VCard21Test.php @@ -0,0 +1,52 @@ +serialize($input); + + $this->assertEquals($input, $output); + + } + + function testPropertyPadValueCount() { + + $input = <<serialize($input); + + $expected = <<assertEquals($expected, $output); + + } +} diff --git a/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php b/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php @@ -0,0 +1,531 @@ +convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert40to40() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert21to40() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert30to30() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert40to30() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvertGroupCard() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testBDAYConversion() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownSourceVCardVersion() { + + $input = <<convert(Document::VCARD40); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownTargetVCardVersion() { + + $input = <<convert(Document::VCARD21); + + } + + function testConvertIndividualCard() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testAnniversary() { + + $input = <<!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = array( + $output, + $input + ); + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testMultipleAnniversaries() { + + $input = <<!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210 +ITEM2.X-ABLABEL:_$!!$_ +ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210 +ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210 +ITEM3.X-ABLABEL:_$!!$_ +ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = array( + $output, + $input + ); + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testNoLabel() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(Document::VCARD40); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $version = Version::VERSION; + + $expected = <<assertEquals($expected, str_replace("\r","", $vcard)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/VersionTest.php b/vendor/sabre/vobject/tests/VObject/VersionTest.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/VersionTest.php @@ -0,0 +1,14 @@ +assertEquals(-1, version_compare('2.0.0',$v)); + + } + +} diff --git a/vendor/sabre/vobject/tests/VObject/issue153.vcf b/vendor/sabre/vobject/tests/VObject/issue153.vcf new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/issue153.vcf @@ -0,0 +1,352 @@ +BEGIN:VCARD +VERSION:3.0 +N:Benutzer;Test;;; +FN:Test Benutzer +PHOTO;BASE64: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + 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 + zbVbk4/OrNpefLsnyyg5UUAf/9k= +END:VCARD diff --git a/vendor/sabre/vobject/tests/VObject/issue64.vcf b/vendor/sabre/vobject/tests/VObject/issue64.vcf new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/VObject/issue64.vcf @@ -0,0 +1,351 @@ +BEGIN:VCARD +VERSION:2.1 +PHOTO;ENCODING=BASE64;JPEG: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + 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 + zbVbk4/OrNpefLsnyyg5UUAf/9k= + +END:VCARD diff --git a/vendor/sabre/vobject/tests/bootstrap.php b/vendor/sabre/vobject/tests/bootstrap.php new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/bootstrap.php @@ -0,0 +1,25 @@ +addPsr4('Sabre\\VObject\\',__DIR__ . '/VObject'); + +if (!defined('SABRE_TEMPDIR')) { + define('SABRE_TEMPDIR', __DIR__ . '/temp/'); +} + +if (!file_exists(SABRE_TEMPDIR)) { + mkdir(SABRE_TEMPDIR); +} diff --git a/vendor/sabre/vobject/tests/phpcs/ruleset.xml b/vendor/sabre/vobject/tests/phpcs/ruleset.xml new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ + + + sabre.io codesniffer ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/sabre/vobject/tests/phpunit.xml b/vendor/sabre/vobject/tests/phpunit.xml new file mode 100644 --- /dev/null +++ b/vendor/sabre/vobject/tests/phpunit.xml @@ -0,0 +1,21 @@ + + + VObject/ + + + + + ../lib/ + + ../lib/Sabre/VObject/includes.php + + + +